Compare commits
72 Commits
1.4.10-openssl
...
1.4.15
| Author | SHA1 | Date | |
|---|---|---|---|
| eaca25ea30 | |||
| 6cd481e824 | |||
| 1686ce095f | |||
| 6150957689 | |||
| 3b4d519178 | |||
| 35c852b2cd | |||
| bd7ff3e4e0 | |||
| f5d5766644 | |||
| 0a3585f4f8 | |||
| a8bd42fd3f | |||
| 38d6ad4920 | |||
| 2a1f0187ac | |||
| 4f5a4dc993 | |||
| 7d0db0dea0 | |||
| add439de00 | |||
| 4a7a9e7228 | |||
| a78c36c999 | |||
| a80c90f025 | |||
| 28b13093f6 | |||
| 7dcf4a54ef | |||
| 7fdd272d76 | |||
| d5ce71b769 | |||
| fa7a390fe3 | |||
| e49b091b92 | |||
| 8c3d756842 | |||
| 97cf371362 | |||
| f14e5e0148 | |||
| e9388e5e5e | |||
| 669b3ae349 | |||
| bd9cca6fb1 | |||
| 8284748381 | |||
| 7aa37e40b9 | |||
| eab2155384 | |||
| 3e36d70164 | |||
| 3dea4906e1 | |||
| 72abd7e20e | |||
| c60119af00 | |||
| 1e0c9eabe3 | |||
| 3f700e79d3 | |||
| 7a974677fb | |||
| 4c91a7a3bf | |||
| ad51f8118d | |||
| 071a6533e0 | |||
| 9f24a71aed | |||
| 94453894e9 | |||
| 9c7223d016 | |||
| e01811e20e | |||
| 6e6eee39d4 | |||
| 68cfab1ac9 | |||
| 8e16309930 | |||
| b7953f5535 | |||
| ce5d5c5cfa | |||
| b0c8f04a53 | |||
| c59346ea68 | |||
| 14d2578a67 | |||
| f03af7a9bf | |||
| a23002ce66 | |||
| ed7cbd38e8 | |||
| cbb0bd6864 | |||
| 6e1323cc23 | |||
| 006fd6ebec | |||
| abcd35e443 | |||
| 9e964b3ea8 | |||
| bfdf940dbf | |||
| 68716f38e0 | |||
| c7751efa71 | |||
| b987583770 | |||
| 1a3235697e | |||
| f6058d9ac0 | |||
| ffa691ac78 | |||
| 1d413f9b76 | |||
| 90b1646876 |
@@ -61,6 +61,8 @@ find_package(Opus REQUIRED)
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(Jemalloc REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
message("${zstd_DIR}")
|
||||
find_package(zstd REQUIRED)
|
||||
|
||||
include_directories(${StringVariable_INCLUDE_DIR})
|
||||
add_subdirectory(music/)
|
||||
|
||||
+8
-2
@@ -12,21 +12,27 @@ add_library(TeaSpeak-FileServer STATIC
|
||||
local_server/LocalFileTransferDisk.cpp
|
||||
local_server/LocalFileTransferNetwork.cpp
|
||||
local_server/clnpath.cpp
|
||||
local_server/NetTools.cpp
|
||||
local_server/Config.cpp
|
||||
local_server/HTTPUtils.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(TeaSpeak-FileServer PUBLIC TeaSpeak ${StringVariable_LIBRARIES_STATIC} stdc++fs
|
||||
libevent::core libevent::pthreads
|
||||
DataPipes::core::static
|
||||
# We're not linking this here, since we may later use DataPipes::shared linking
|
||||
# DataPipes::core::static
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
)
|
||||
|
||||
target_include_directories(TeaSpeak-FileServer PUBLIC include/)
|
||||
target_compile_options(TeaSpeak-FileServer PUBLIC "-Wswitch-enum")
|
||||
|
||||
add_executable(TeaSpeak-FileServerTest test/main.cpp)
|
||||
target_link_libraries(TeaSpeak-FileServerTest PUBLIC TeaSpeak-FileServer
|
||||
TeaMusic #Static (Must be in here, so we link against TeaMusic which uses C++11. That forbids GCC to use the newer glibc version)
|
||||
CXXTerminal::static #Static
|
||||
CXXTerminal::static
|
||||
DataPipes::core::static
|
||||
stdc++fs
|
||||
)
|
||||
target_compile_options(TeaSpeak-FileServerTest PUBLIC -static-libgcc -static-libstdc++)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <pipes/ssl.h>
|
||||
|
||||
namespace ts::server::file::config {
|
||||
enum struct Key {
|
||||
SSL_OPTION_SUPPLIER
|
||||
};
|
||||
|
||||
extern void value_updated(Key /* value */);
|
||||
extern std::function<std::shared_ptr<pipes::SSL::Options>()> ssl_option_supplier;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
namespace ts::server::file {
|
||||
enum struct ExecuteStatus {
|
||||
UNKNOWN,
|
||||
WAITING,
|
||||
SUCCESS,
|
||||
ERROR
|
||||
};
|
||||
|
||||
template<typename VariantType, typename T, std::size_t index = 0>
|
||||
constexpr std::size_t variant_index() {
|
||||
if constexpr (index == std::variant_size_v<VariantType>) {
|
||||
return index;
|
||||
} else if constexpr (std::is_same_v<std::variant_alternative_t<index, VariantType>, T>) {
|
||||
return index;
|
||||
} else {
|
||||
return variant_index<VariantType, T, index + 1>();
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyExecuteResponse { };
|
||||
template <class error_t, class response_t = EmptyExecuteResponse>
|
||||
class ExecuteResponse {
|
||||
typedef std::variant<EmptyExecuteResponse, error_t, response_t> variant_t;
|
||||
public:
|
||||
ExecuteStatus status{ExecuteStatus::WAITING};
|
||||
|
||||
[[nodiscard]] inline auto response() const -> const response_t& { return std::get<response_t>(this->response_); }
|
||||
|
||||
template <typename = std::enable_if_t<!std::is_void<error_t>::value>>
|
||||
[[nodiscard]] inline const error_t& error() const { return std::get<error_t>(this->response_); }
|
||||
|
||||
inline void wait() const {
|
||||
std::unique_lock nlock{this->notify_mutex};
|
||||
this->notify_cv.wait(nlock, [&]{ return this->status != ExecuteStatus::WAITING; });
|
||||
}
|
||||
|
||||
template<typename _Rep, typename _Period>
|
||||
[[nodiscard]] inline bool wait_for(const std::chrono::duration<_Rep, _Period>& time) const {
|
||||
std::unique_lock nlock{this->notify_mutex};
|
||||
return this->notify_cv.wait_for(nlock, time, [&]{ return this->status != ExecuteStatus::WAITING; });
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void emplace_success(Args&&... args) {
|
||||
constexpr auto success_index = variant_index<variant_t, response_t>();
|
||||
|
||||
std::lock_guard rlock{this->notify_mutex};
|
||||
this->response_.template emplace<success_index, Args...>(std::forward<Args>(args)...);
|
||||
this->status = ExecuteStatus::SUCCESS;
|
||||
this->notify_cv.notify_all();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void emplace_fail(Args&&... args) {
|
||||
constexpr auto error_index = variant_index<variant_t, error_t>();
|
||||
|
||||
std::lock_guard rlock{this->notify_mutex};
|
||||
this->response_.template emplace<error_index, Args...>(std::forward<Args>(args)...);
|
||||
this->status = ExecuteStatus::ERROR;
|
||||
this->notify_cv.notify_all();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool succeeded() const {
|
||||
return this->status == ExecuteStatus::SUCCESS;
|
||||
}
|
||||
|
||||
ExecuteResponse(std::mutex& notify_mutex, std::condition_variable& notify_cv)
|
||||
: notify_mutex{notify_mutex}, notify_cv{notify_cv} {}
|
||||
private:
|
||||
variant_t response_{}; /* void* as default value so we don't initialize error_t or response_t */
|
||||
|
||||
std::mutex& notify_mutex;
|
||||
std::condition_variable& notify_cv;
|
||||
};
|
||||
}
|
||||
+215
-102
@@ -4,86 +4,19 @@
|
||||
#include <chrono>
|
||||
#include <Definitions.h>
|
||||
#include <condition_variable>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
#include "./ExecuteResponse.h"
|
||||
|
||||
#define TRANSFER_KEY_LENGTH (32)
|
||||
#define TRANSFER_MEDIA_BYTES_LENGTH (32)
|
||||
|
||||
namespace ts::server::file {
|
||||
enum struct ExecuteStatus {
|
||||
UNKNOWN,
|
||||
WAITING,
|
||||
SUCCESS,
|
||||
ERROR
|
||||
};
|
||||
|
||||
template<typename VariantType, typename T, std::size_t index = 0>
|
||||
constexpr std::size_t variant_index() {
|
||||
if constexpr (index == std::variant_size_v<VariantType>) {
|
||||
return index;
|
||||
} else if constexpr (std::is_same_v<std::variant_alternative_t<index, VariantType>, T>) {
|
||||
return index;
|
||||
} else {
|
||||
return variant_index<VariantType, T, index + 1>();
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyExecuteResponse { };
|
||||
template <class error_t, class response_t = EmptyExecuteResponse>
|
||||
class ExecuteResponse {
|
||||
typedef std::variant<EmptyExecuteResponse, error_t, response_t> variant_t;
|
||||
public:
|
||||
ExecuteStatus status{ExecuteStatus::WAITING};
|
||||
|
||||
[[nodiscard]] inline const auto& response() const { return std::get<response_t>(this->response_); }
|
||||
|
||||
template <typename = std::enable_if_t<!std::is_void<error_t>::value>>
|
||||
[[nodiscard]] inline const error_t& error() const { return std::get<error_t>(this->response_); }
|
||||
|
||||
inline void wait() const {
|
||||
std::unique_lock nlock{this->notify_mutex};
|
||||
this->notify_cv.wait(nlock, [&]{ return this->status != ExecuteStatus::WAITING; });
|
||||
}
|
||||
|
||||
template<typename _Rep, typename _Period>
|
||||
[[nodiscard]] inline bool wait_for(const std::chrono::duration<_Rep, _Period>& time) const {
|
||||
std::unique_lock nlock{this->notify_mutex};
|
||||
return this->notify_cv.wait_for(nlock, time, [&]{ return this->status != ExecuteStatus::WAITING; });
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void emplace_success(Args&&... args) {
|
||||
constexpr auto success_index = variant_index<variant_t, response_t>();
|
||||
|
||||
std::lock_guard rlock{this->notify_mutex};
|
||||
this->response_.template emplace<success_index, Args...>(std::forward<Args>(args)...);
|
||||
this->status = ExecuteStatus::SUCCESS;
|
||||
this->notify_cv.notify_all();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void emplace_fail(Args&&... args) {
|
||||
constexpr auto error_index = variant_index<variant_t, error_t>();
|
||||
|
||||
std::lock_guard rlock{this->notify_mutex};
|
||||
this->response_.template emplace<error_index, Args...>(std::forward<Args>(args)...);
|
||||
this->status = ExecuteStatus::ERROR;
|
||||
this->notify_cv.notify_all();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool succeeded() const {
|
||||
return this->status == ExecuteStatus::SUCCESS;
|
||||
}
|
||||
|
||||
ExecuteResponse(std::mutex& notify_mutex, std::condition_variable& notify_cv)
|
||||
: notify_mutex{notify_mutex}, notify_cv{notify_cv} {}
|
||||
private:
|
||||
variant_t response_{}; /* void* as default value so we don't initialize error_t or response_t */
|
||||
|
||||
std::mutex& notify_mutex;
|
||||
std::condition_variable& notify_cv;
|
||||
};
|
||||
class VirtualFileServer;
|
||||
|
||||
namespace filesystem {
|
||||
template <typename ErrorCodes>
|
||||
@@ -99,11 +32,9 @@ namespace ts::server::file {
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_IS_A_FILE,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
FAILED_TO_LIST_FILES,
|
||||
|
||||
MAX
|
||||
FAILED_TO_LIST_FILES
|
||||
};
|
||||
constexpr std::array<std::string_view, (int) DirectoryQueryErrorType::MAX> directory_query_error_messages = {
|
||||
constexpr std::array<std::string_view, 5> directory_query_error_messages = {
|
||||
"unknown error",
|
||||
"path exceeds base path",
|
||||
"path is a file",
|
||||
@@ -124,7 +55,8 @@ namespace ts::server::file {
|
||||
std::string name{};
|
||||
std::chrono::system_clock::time_point modified_at{};
|
||||
|
||||
size_t size{0};
|
||||
size_t size{0}; /* file only */
|
||||
bool empty{false}; /* directory only */
|
||||
};
|
||||
|
||||
enum struct DirectoryModifyErrorType {
|
||||
@@ -143,11 +75,38 @@ namespace ts::server::file {
|
||||
TARGET_PATH_ALREADY_EXISTS,
|
||||
FAILED_TO_DELETE_FILES,
|
||||
FAILED_TO_RENAME_FILE,
|
||||
FAILED_TO_CREATE_DIRECTORIES,
|
||||
|
||||
SOME_FILES_ARE_LOCKED
|
||||
};
|
||||
typedef DetailedError<FileModifyErrorType> FileModifyError;
|
||||
|
||||
enum struct FileDeleteErrorType {
|
||||
UNKNOWN,
|
||||
};
|
||||
typedef DetailedError<FileDeleteErrorType> FileDeleteError;
|
||||
|
||||
struct FileDeleteResponse {
|
||||
enum struct StatusType {
|
||||
SUCCESS,
|
||||
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
FAILED_TO_DELETE_FILES,
|
||||
SOME_FILES_ARE_LOCKED
|
||||
};
|
||||
|
||||
struct DeleteResult {
|
||||
StatusType status{StatusType::SUCCESS};
|
||||
std::string error_detail{};
|
||||
|
||||
DeleteResult(StatusType status, std::string errorDetail) : status{status},
|
||||
error_detail{std::move(errorDetail)} {}
|
||||
};
|
||||
|
||||
std::vector<DeleteResult> delete_results{};
|
||||
};
|
||||
|
||||
enum struct ServerCommandErrorType {
|
||||
UNKNOWN,
|
||||
FAILED_TO_CREATE_DIRECTORIES,
|
||||
@@ -155,42 +114,76 @@ namespace ts::server::file {
|
||||
};
|
||||
typedef DetailedError<ServerCommandErrorType> ServerCommandError;
|
||||
|
||||
struct FileInfoResponse {
|
||||
enum struct StatusType {
|
||||
SUCCESS,
|
||||
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
|
||||
FAILED_TO_QUERY_INFO,
|
||||
UNKNOWN_FILE_TYPE
|
||||
};
|
||||
|
||||
struct FileInfo {
|
||||
StatusType status{StatusType::SUCCESS};
|
||||
std::string error_detail{};
|
||||
|
||||
DirectoryEntry info{};
|
||||
|
||||
FileInfo(StatusType status, std::string errorDetail, DirectoryEntry info) : status{status},
|
||||
error_detail{std::move(errorDetail)}, info{std::move(info)} {}
|
||||
};
|
||||
|
||||
std::vector<FileInfo> file_info{};
|
||||
};
|
||||
|
||||
enum struct FileInfoErrorType {
|
||||
UNKNOWN,
|
||||
};
|
||||
typedef DetailedError<FileInfoErrorType> FileInfoError;
|
||||
|
||||
class AbstractProvider {
|
||||
public:
|
||||
typedef ExecuteResponse<DirectoryQueryError, std::deque<DirectoryEntry>> directory_query_response_t;
|
||||
|
||||
/* server */
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(ServerId /* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(ServerId /* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
|
||||
/* channels */
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_channel_directory(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<DirectoryModifyError>> create_channel_directory(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> delete_channel_file(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> rename_channel_file(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */, const std::string& /* target */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_channel_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* files */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<DirectoryModifyError>> create_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_channel_files(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::vector<std::string>& /* paths */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> rename_channel_file(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */, ChannelId /* target channel */, const std::string& /* target */) = 0;
|
||||
|
||||
/* icons */
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_icon_directory(ServerId /* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> delete_icon(ServerId /* server */, const std::string& /* name */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_icon_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_icons(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
|
||||
/* avatars */
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_avatar_directory(ServerId /* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> delete_avatar(ServerId /* server */, const std::string& /* name */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_avatar_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_avatars(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
||||
namespace transfer {
|
||||
typedef uint32_t transfer_id;
|
||||
typedef uint16_t transfer_id;
|
||||
|
||||
struct Transfer {
|
||||
transfer_id server_transfer_id{0};
|
||||
transfer_id client_transfer_id{0};
|
||||
|
||||
ServerId server_id{0};
|
||||
ClientId client_id{0};
|
||||
std::shared_ptr<VirtualFileServer> server{nullptr};
|
||||
ChannelId channel_id{0};
|
||||
|
||||
ClientId client_id{0};
|
||||
std::string client_unique_id{};
|
||||
|
||||
char transfer_key[TRANSFER_KEY_LENGTH]{};
|
||||
std::chrono::system_clock::time_point initialized_timestamp{};
|
||||
enum Direction {
|
||||
@@ -212,6 +205,10 @@ namespace ts::server::file {
|
||||
TARGET_TYPE_AVATAR
|
||||
} target_type{TARGET_TYPE_UNKNOWN};
|
||||
std::string target_file_path{};
|
||||
std::string absolute_file_path{};
|
||||
|
||||
std::string relative_file_path{};
|
||||
std::string file_name{};
|
||||
|
||||
int64_t max_bandwidth{-1};
|
||||
size_t expected_file_size{0}; /* incl. the offset! */
|
||||
@@ -232,13 +229,31 @@ namespace ts::server::file {
|
||||
size_t file_start_offset{0};
|
||||
size_t file_current_offset{0};
|
||||
size_t file_total_size{0};
|
||||
|
||||
double average_speed{0};
|
||||
double current_speed{0};
|
||||
};
|
||||
|
||||
struct TransferInitError {
|
||||
enum Type {
|
||||
UNKNOWN
|
||||
UNKNOWN,
|
||||
|
||||
INVALID_FILE_TYPE,
|
||||
FILE_DOES_NOT_EXISTS,
|
||||
FILE_IS_NOT_A_FILE,
|
||||
|
||||
CLIENT_TOO_MANY_TRANSFERS,
|
||||
SERVER_TOO_MANY_TRANSFERS,
|
||||
|
||||
SERVER_QUOTA_EXCEEDED,
|
||||
CLIENT_QUOTA_EXCEEDED,
|
||||
|
||||
IO_ERROR
|
||||
} error_type{UNKNOWN};
|
||||
std::string error_message{};
|
||||
|
||||
TransferInitError(Type errorType, std::string errorMessage) : error_type{errorType},
|
||||
error_message{std::move(errorMessage)} {}
|
||||
};
|
||||
|
||||
struct TransferActionError {
|
||||
@@ -263,44 +278,142 @@ namespace ts::server::file {
|
||||
NETWORK_IO_ERROR,
|
||||
|
||||
UNEXPECTED_CLIENT_DISCONNECT,
|
||||
UNEXPECTED_DISK_EOF
|
||||
UNEXPECTED_DISK_EOF,
|
||||
|
||||
USER_REQUEST
|
||||
} error_type{UNKNOWN};
|
||||
std::string error_message{};
|
||||
};
|
||||
|
||||
struct ActiveFileTransfer {
|
||||
transfer_id client_transfer_id{0};
|
||||
transfer_id server_transfer_id{0};
|
||||
|
||||
Transfer::Direction direction{Transfer::DIRECTION_UNKNOWN};
|
||||
|
||||
ClientId client_id{};
|
||||
std::string client_unique_id{};
|
||||
|
||||
std::string file_path{};
|
||||
std::string file_name{};
|
||||
|
||||
size_t expected_size{};
|
||||
size_t size_done{};
|
||||
|
||||
enum Status {
|
||||
NOT_STARTED,
|
||||
RUNNING,
|
||||
INACTIVE /* (not used yet) */
|
||||
} status{Status::NOT_STARTED};
|
||||
|
||||
std::chrono::milliseconds runtime{};
|
||||
|
||||
double average_speed{0};
|
||||
double current_speed{0};
|
||||
};
|
||||
|
||||
enum struct TransferListError {
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
class AbstractProvider {
|
||||
public:
|
||||
struct TransferInfo {
|
||||
std::string file_path{};
|
||||
std::string client_unique_id{};
|
||||
ClientId client_id{};
|
||||
|
||||
bool override_exiting{false}; /* only for upload valid */
|
||||
|
||||
size_t file_offset{0};
|
||||
size_t expected_file_size{0};
|
||||
|
||||
int64_t max_bandwidth{-1};
|
||||
int64_t max_concurrent_transfers{-1};
|
||||
|
||||
/* only used for upload, for download the quotas could be checked before */
|
||||
size_t download_server_quota_limit{(size_t) -1};
|
||||
size_t download_client_quota_limit{(size_t) -1};
|
||||
};
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> initialize_channel_transfer(Transfer::Direction /* direction */, ServerId /* server */, ChannelId /* channel */, const TransferInfo& /* info */) = 0;
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> initialize_icon_transfer(Transfer::Direction /* direction */, ServerId /* server */, const TransferInfo& /* info */) = 0;
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> initialize_avatar_transfer(Transfer::Direction /* direction */, ServerId /* server */, const TransferInfo& /* info */) = 0;
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_channel_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, ChannelId /* channel */, const TransferInfo& /* info */) = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(transfer_id /* id */, bool /* flush */) = 0;
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_icon_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_avatar_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> list_transfer() = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(const std::shared_ptr<VirtualFileServer>& /* server */, transfer_id /* id */, bool /* flush */) = 0;
|
||||
|
||||
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_registered{}; /* transfer has been registered */
|
||||
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_started{}; /* transfer has been started */
|
||||
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_finished{}; /* transfer has been finished */
|
||||
std::function<void(const std::shared_ptr<Transfer>&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */
|
||||
std::function<void(const std::shared_ptr<Transfer>&, const TransferStatistics&)> callback_transfer_statistics{};
|
||||
std::function<void(const std::shared_ptr<Transfer>&, const transfer::TransferStatistics&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */
|
||||
};
|
||||
}
|
||||
|
||||
class AbstractFileServer {
|
||||
class VirtualFileServer {
|
||||
public:
|
||||
[[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0;
|
||||
[[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0;
|
||||
explicit VirtualFileServer(ServerId server_id, std::string unique_id) : server_id_{server_id}, unique_id_{std::move(unique_id)} {}
|
||||
|
||||
[[nodiscard]] inline auto unique_id() const -> const std::string& { return this->unique_id_; }
|
||||
[[nodiscard]] inline auto server_id() const -> ServerId { return this->server_id_; }
|
||||
|
||||
[[nodiscard]] inline auto max_networking_upload_bandwidth() const -> int64_t { return this->max_networking_upload_bandwidth_; }
|
||||
virtual void max_networking_upload_bandwidth(int64_t value) {
|
||||
this->max_networking_upload_bandwidth_ = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto max_networking_download_bandwidth() const -> int64_t { return this->max_networking_download_bandwidth_; }
|
||||
virtual void max_networking_download_bandwidth(int64_t value) {
|
||||
this->max_networking_download_bandwidth_ = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto generate_transfer_id() {
|
||||
return ++this->current_transfer_id;
|
||||
}
|
||||
private:
|
||||
ServerId server_id_;
|
||||
std::string unique_id_;
|
||||
|
||||
int64_t max_networking_upload_bandwidth_{-1};
|
||||
int64_t max_networking_download_bandwidth_{-1};
|
||||
|
||||
std::atomic<transfer::transfer_id> current_transfer_id{0};
|
||||
};
|
||||
|
||||
extern bool initialize(std::string& /* error */);
|
||||
class AbstractFileServer {
|
||||
public:
|
||||
[[nodiscard]] virtual std::string file_base_path() const = 0;
|
||||
[[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0;
|
||||
[[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0;
|
||||
|
||||
[[nodiscard]] inline auto virtual_servers() const -> std::deque<std::shared_ptr<VirtualFileServer>> {
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
return this->servers_;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto find_virtual_server(ServerId server_id) const -> std::shared_ptr<VirtualFileServer> {
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
|
||||
return server->server_id() == server_id;
|
||||
});
|
||||
return it == this->servers_.end() ? nullptr : *it;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) = 0;
|
||||
virtual void unregister_server(ServerId /* server id */) = 0;
|
||||
protected:
|
||||
mutable std::mutex servers_mutex{};
|
||||
std::deque<std::shared_ptr<VirtualFileServer>> servers_{};
|
||||
};
|
||||
|
||||
extern bool initialize(std::string& /* error */, const std::string& /* host names */, uint16_t /* port */);
|
||||
extern void finalize();
|
||||
|
||||
extern std::shared_ptr<AbstractFileServer> server();
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Created by WolverinDEV on 21/05/2020.
|
||||
//
|
||||
|
||||
#include "files/Config.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
|
||||
std::function<std::shared_ptr<pipes::SSL::Options>()> config::ssl_option_supplier{nullptr};
|
||||
|
||||
void config::value_updated(ts::server::file::config::Key) {
|
||||
/* we currently do nothing */
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Created by WolverinDEV on 22/05/2020.
|
||||
//
|
||||
|
||||
#include <pipes/misc/http.h>
|
||||
#include "HTTPUtils.h"
|
||||
|
||||
bool http::parse_url_parameters(const std::string_view &query, std::map<std::string, std::string>& result) {
|
||||
const auto query_offset = query.find('?');
|
||||
if(query_offset == std::string::npos) return false;
|
||||
|
||||
const auto query_end_offset = query.find('#', query_offset); /* fragment (if there is any) */
|
||||
|
||||
auto offset = query_offset + 1;
|
||||
size_t next_param;
|
||||
while(offset > 0) {
|
||||
next_param = query.find('&', offset);
|
||||
if(next_param >= query_end_offset)
|
||||
next_param = query_end_offset;
|
||||
|
||||
if(offset >= next_param)
|
||||
break;
|
||||
|
||||
/* parameter: [offset;next_param) */
|
||||
const auto param_view = query.substr(offset, next_param - offset);
|
||||
const auto assignment_index = param_view.find('=');
|
||||
if(assignment_index == std::string::npos)
|
||||
result[std::string{param_view}] = "";
|
||||
else
|
||||
result[std::string{param_view.substr(0, assignment_index)}] = http::decode_url(std::string{param_view.substr(assignment_index + 1)});
|
||||
|
||||
offset = next_param + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace http {
|
||||
bool parse_url_parameters(const std::string_view& /* url */, std::map<std::string, std::string>& /* result */);
|
||||
}
|
||||
@@ -3,66 +3,63 @@
|
||||
//
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "LocalFileProvider.h"
|
||||
#include "LocalFileSystem.h"
|
||||
#include "LocalFileTransfer.h"
|
||||
|
||||
using namespace ts::server;
|
||||
using LocalFileServer = file::LocalFileProvider;
|
||||
|
||||
EVP_PKEY* ssl_generate_key() {
|
||||
auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
|
||||
|
||||
auto rsa = RSA_new();
|
||||
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(BN_new(), ::BN_free);
|
||||
BN_set_word(e.get(), RSA_F4);
|
||||
if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return nullptr;
|
||||
EVP_PKEY_assign_RSA(key.get(), rsa);
|
||||
return key.release();
|
||||
}
|
||||
|
||||
X509* ssl_generate_certificate(EVP_PKEY* key) {
|
||||
auto cert = X509_new();
|
||||
X509_set_pubkey(cert, key);
|
||||
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(cert), 3);
|
||||
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);
|
||||
|
||||
X509_NAME* name = nullptr;
|
||||
name = X509_get_subject_name(cert);
|
||||
//for(const auto& subject : this->subjects)
|
||||
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
|
||||
X509_set_subject_name(cert, name);
|
||||
|
||||
name = X509_get_issuer_name(cert);
|
||||
//for(const auto& subject : this->issues)
|
||||
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
|
||||
|
||||
X509_set_issuer_name(cert, name);
|
||||
|
||||
X509_sign(cert, key, EVP_sha512());
|
||||
return cert;
|
||||
}
|
||||
using LocalVirtualFileServer = file::LocalVirtualFileServer;
|
||||
|
||||
std::shared_ptr<LocalFileServer> server_instance{};
|
||||
bool file::initialize(std::string &error) {
|
||||
bool file::initialize(std::string &error, const std::string& hostnames, uint16_t port) {
|
||||
server_instance = std::make_shared<LocalFileProvider>();
|
||||
|
||||
auto options = std::make_shared<pipes::SSL::Options>();
|
||||
options->verbose_io = true;
|
||||
options->context_method = SSLv23_method();
|
||||
options->free_unused_keypairs = false;
|
||||
|
||||
{
|
||||
std::shared_ptr<EVP_PKEY> pkey{ssl_generate_key(), ::EVP_PKEY_free};
|
||||
std::shared_ptr<X509> cert{ssl_generate_certificate(&*pkey), ::X509_free};
|
||||
|
||||
options->default_keypair({pkey, cert});
|
||||
}
|
||||
|
||||
if(!server_instance->initialize(error, options)) {
|
||||
if(!server_instance->initialize(error)) {
|
||||
server_instance = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool any_bind{false};
|
||||
for(const auto& binding : net::resolve_bindings(hostnames, port)) {
|
||||
if(!get<2>(binding).empty()) {
|
||||
logError(LOG_FT, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto result = dynamic_cast<transfer::LocalFileTransfer&>(server_instance->file_transfer()).add_network_binding({ get<0>(binding), get<1>(binding) });
|
||||
switch (result) {
|
||||
case transfer::NetworkingBindResult::SUCCESS:
|
||||
any_bind = true;
|
||||
break;
|
||||
|
||||
case transfer::NetworkingBindResult::OUT_OF_MEMORY:
|
||||
logWarning(LOG_FT, "Failed to listen to address {}: Out of memory", get<0>(binding));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::FAILED_TO_LISTEN:
|
||||
logWarning(LOG_FT, "Failed to listen on {}: {}/{}", get<0>(binding), errno, strerror(errno));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::FAILED_TO_BIND:
|
||||
logWarning(LOG_FT, "Failed to bind on {}: {}/{}", get<0>(binding), errno, strerror(errno));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::BINDING_ALREADY_EXISTS:
|
||||
logWarning(LOG_FT, "Failed to bind on {}: binding already exists", get<0>(binding));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::NETWORKING_NOT_INITIALIZED:
|
||||
logWarning(LOG_FT, "Failed to bind on {}: networking not initialized", get<0>(binding));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::FAILED_TO_ALLOCATE_SOCKET:
|
||||
logWarning(LOG_FT, "Failed to allocate a socket for {}: {}/{}", get<0>(binding), errno, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -77,45 +74,75 @@ std::shared_ptr<file::AbstractFileServer> file::server() {
|
||||
return server_instance;
|
||||
}
|
||||
|
||||
LocalFileServer::LocalFileProvider() : file_system_{}, file_transfer_{this->file_system_} {}
|
||||
LocalFileServer::~LocalFileProvider() {}
|
||||
LocalFileServer::LocalFileProvider() {
|
||||
this->file_system_ = new filesystem::LocalFileSystem();
|
||||
this->file_transfer_ = new transfer::LocalFileTransfer(this->file_system_);
|
||||
}
|
||||
LocalFileServer::~LocalFileProvider() {
|
||||
delete this->file_transfer_;
|
||||
delete this->file_system_;
|
||||
};
|
||||
|
||||
bool LocalFileServer::initialize(std::string &error, const std::shared_ptr<pipes::SSL::Options>& ssl_options) {
|
||||
if(!this->file_system_.initialize(error, "file-root/"))
|
||||
bool LocalFileServer::initialize(std::string &error) {
|
||||
if(!this->file_system_->initialize(error, "files/"))
|
||||
return false;
|
||||
|
||||
|
||||
std::deque<std::shared_ptr<transfer::NetworkBinding>> bindings{};
|
||||
{
|
||||
auto binding = std::make_shared<transfer::NetworkBinding>();
|
||||
|
||||
binding->hostname = "localhost";
|
||||
|
||||
auto& iaddr = *(sockaddr_in*) &binding->address;
|
||||
iaddr.sin_family = AF_INET;
|
||||
iaddr.sin_port = htons(1112);
|
||||
iaddr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
bindings.push_back(std::move(binding));
|
||||
}
|
||||
|
||||
if(!this->file_transfer_.start(bindings, ssl_options)) {
|
||||
if(!this->file_transfer_->start()) {
|
||||
error = "transfer server startup failed";
|
||||
this->file_system_.finalize();
|
||||
this->file_system_->finalize();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalFileServer::finalize() {
|
||||
this->file_transfer_.stop();
|
||||
this->file_system_.finalize();
|
||||
this->file_transfer_->stop();
|
||||
this->file_system_->finalize();
|
||||
}
|
||||
|
||||
file::filesystem::AbstractProvider &LocalFileServer::file_system() {
|
||||
return this->file_system_;
|
||||
return *this->file_system_;
|
||||
}
|
||||
|
||||
file::transfer::AbstractProvider & LocalFileServer::file_transfer() {
|
||||
return this->file_transfer_;
|
||||
file::transfer::AbstractProvider &LocalFileServer::file_transfer() {
|
||||
return *this->file_transfer_;
|
||||
}
|
||||
|
||||
std::string file::LocalFileProvider::file_base_path() const {
|
||||
return this->file_system_->root_path();
|
||||
}
|
||||
|
||||
std::shared_ptr<file::VirtualFileServer> LocalFileServer::register_server(ServerId server_id) {
|
||||
auto server = this->find_virtual_server(server_id);
|
||||
if(server) return server;
|
||||
|
||||
server = std::make_shared<file::LocalVirtualFileServer>(server_id, std::to_string(server_id));
|
||||
{
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
this->servers_.push_back(server);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void LocalFileServer::unregister_server(ServerId server_id) {
|
||||
auto server_unique_id = std::to_string(server_id);
|
||||
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
|
||||
return server->unique_id() == server_unique_id;
|
||||
});
|
||||
|
||||
if(it == this->servers_.end()) return;
|
||||
this->servers_.erase(it);
|
||||
}
|
||||
|
||||
void LocalVirtualFileServer::max_networking_upload_bandwidth(int64_t value) {
|
||||
VirtualFileServer::max_networking_upload_bandwidth(value);
|
||||
this->upload_throttle.set_max_bandwidth(value);
|
||||
}
|
||||
|
||||
void LocalVirtualFileServer::max_networking_download_bandwidth(int64_t value) {
|
||||
VirtualFileServer::max_networking_download_bandwidth(value);
|
||||
this->download_throttle.set_max_bandwidth(value);
|
||||
}
|
||||
@@ -9,539 +9,44 @@
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <random>
|
||||
|
||||
#define TRANSFER_MAX_CACHED_BYTES (1024 * 1024 * 1) // Buffer up to 1mb
|
||||
#include <misc/memtracker.h>
|
||||
#include "./NetTools.h"
|
||||
|
||||
namespace ts::server::file {
|
||||
namespace filesystem {
|
||||
#ifdef FS_INCLUDED
|
||||
namespace fs = std::experimental::filesystem;
|
||||
#endif
|
||||
namespace filesystem { class LocalFileSystem; }
|
||||
namespace transfer { class LocalFileTransfer; }
|
||||
|
||||
class LocalFileSystem : public filesystem::AbstractProvider {
|
||||
using FileModifyError = filesystem::FileModifyError;
|
||||
using DirectoryModifyError = filesystem::DirectoryModifyError;
|
||||
public:
|
||||
enum struct FileCategory {
|
||||
ICON,
|
||||
AVATAR,
|
||||
CHANNEL
|
||||
};
|
||||
class LocalVirtualFileServer : public VirtualFileServer {
|
||||
public:
|
||||
explicit LocalVirtualFileServer(ServerId server_id, std::string unique_id) : VirtualFileServer{server_id, std::move(unique_id)} {}
|
||||
|
||||
virtual ~LocalFileSystem();
|
||||
void max_networking_upload_bandwidth(int64_t value) override;
|
||||
void max_networking_download_bandwidth(int64_t value) override;
|
||||
|
||||
bool initialize(std::string & /* error */, const std::string & /* root path */);
|
||||
void finalize();
|
||||
|
||||
void lock_file(const std::string& /* absolute path */);
|
||||
void unlock_file(const std::string& /* absolute path */);
|
||||
|
||||
[[nodiscard]] inline const auto &root_path() const { return this->root_path_; }
|
||||
|
||||
[[nodiscard]] std::string absolute_avatar_path(ServerId, const std::string&);
|
||||
[[nodiscard]] std::string absolute_icon_path(ServerId, const std::string&);
|
||||
[[nodiscard]] std::string absolute_channel_path(ServerId, ChannelId, const std::string&);
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(ServerId /* server */) override;
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(ServerId /* server */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t>
|
||||
query_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
|
||||
create_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_channel_file(ServerId id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
rename_channel_file(ServerId id, ChannelId channelId, const std::string &, const std::string &) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_icon_directory(ServerId id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_icon(ServerId id, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_avatar_directory(ServerId id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_avatar(ServerId id, const std::string &string) override;
|
||||
|
||||
private:
|
||||
#ifdef FS_INCLUDED
|
||||
[[nodiscard]] fs::path server_path(ServerId);
|
||||
[[nodiscard]] fs::path server_channel_path(ServerId, ChannelId);
|
||||
[[nodiscard]] static bool exceeds_base_path(const fs::path& /* base */, const fs::path& /* target */);
|
||||
[[nodiscard]] bool is_any_file_locked(const fs::path& /* base */, const std::string& /* path */, std::string& /* file (relative to the base) */);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_file(const fs::path& /* base */, const std::string &string);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<directory_query_response_t>
|
||||
query_directory(const fs::path& /* base */, const std::string &string, bool);
|
||||
#endif
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
std::string target_file_path(FileCategory type, ts::ServerId sid, ts::ChannelId cid, const std::string &path);
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::string root_path_{};
|
||||
|
||||
std::mutex locked_files_mutex{};
|
||||
std::deque<std::string> locked_files_{};
|
||||
};
|
||||
}
|
||||
|
||||
namespace transfer {
|
||||
class LocalFileTransfer;
|
||||
|
||||
struct Buffer {
|
||||
Buffer* next{nullptr};
|
||||
|
||||
size_t capacity{0};
|
||||
size_t length{0};
|
||||
size_t offset{0};
|
||||
|
||||
char data[1]{};
|
||||
};
|
||||
[[nodiscard]] extern Buffer* allocate_buffer(size_t);
|
||||
extern void free_buffer(Buffer*);
|
||||
|
||||
struct NetworkThrottle {
|
||||
constexpr static auto kThrottleTimespanMs{250};
|
||||
typedef uint8_t span_t;
|
||||
|
||||
ssize_t max_bytes{0};
|
||||
|
||||
span_t current_index{0};
|
||||
size_t bytes_send{0};
|
||||
|
||||
inline bool increase_bytes(size_t bytes) {
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
if(this->current_index != current_span) {
|
||||
this->current_index = current_span;
|
||||
this->bytes_send = bytes;
|
||||
} else {
|
||||
this->bytes_send += bytes;
|
||||
}
|
||||
return this->max_bytes > 0 && this->bytes_send >= this->max_bytes;
|
||||
}
|
||||
|
||||
inline void set_max_bandwidth(ssize_t bytes_per_second) {
|
||||
if(bytes_per_second <= 0)
|
||||
this->max_bytes = -1;
|
||||
else
|
||||
this->max_bytes = bytes_per_second * kThrottleTimespanMs / 1000;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) {
|
||||
if(this->max_bytes <= 0) return false;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
if(this->current_index != current_span) return false;
|
||||
if(this->bytes_send < this->max_bytes) return false;
|
||||
|
||||
next_timestamp.tv_usec = (kThrottleTimespanMs - current_ms % kThrottleTimespanMs) * 1000;
|
||||
next_timestamp.tv_sec = next_timestamp.tv_usec / 1000000;
|
||||
next_timestamp.tv_usec -= next_timestamp.tv_sec * 1000000;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t bytes_left() const {
|
||||
if(this->max_bytes <= 0) return (size_t) -1;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
if(this->current_index != current_span) return this->max_bytes;
|
||||
if(this->bytes_send < this->max_bytes) return this->max_bytes - this->bytes_send;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds expected_writing_time(size_t bytes) const {
|
||||
if(this->max_bytes <= 0) return std::chrono::milliseconds{0};
|
||||
|
||||
return std::chrono::seconds{bytes / (this->max_bytes * (1000 / kThrottleTimespanMs))};
|
||||
}
|
||||
};
|
||||
|
||||
/* all variables are locked via the state_mutex */
|
||||
struct FileClient : std::enable_shared_from_this<FileClient> {
|
||||
LocalFileTransfer* handle;
|
||||
std::shared_ptr<Transfer> transfer{nullptr};
|
||||
|
||||
std::shared_mutex state_mutex{};
|
||||
enum {
|
||||
STATE_AWAITING_KEY, /* includes SSL/HTTP init */
|
||||
STATE_TRANSFERRING,
|
||||
STATE_DISCONNECTING,
|
||||
STATE_DISCONNECTED
|
||||
} state{STATE_AWAITING_KEY};
|
||||
|
||||
enum NetworkingProtocol {
|
||||
PROTOCOL_UNKNOWN,
|
||||
PROTOCOL_HTTPS,
|
||||
PROTOCOL_TS_V1
|
||||
};
|
||||
|
||||
enum HTTPUploadState {
|
||||
HTTP_AWAITING_HEADER,
|
||||
HTTP_STATE_AWAITING_BOUNDARY,
|
||||
HTTP_STATE_UPLOADING,
|
||||
HTTP_STATE_DOWNLOADING
|
||||
};
|
||||
|
||||
struct {
|
||||
bool file_locked{false};
|
||||
std::string absolute_path{};
|
||||
|
||||
int file_descriptor{0};
|
||||
|
||||
bool currently_processing{false};
|
||||
FileClient* next_client{nullptr};
|
||||
} file{};
|
||||
|
||||
struct {
|
||||
size_t provided_bytes{0};
|
||||
char key[TRANSFER_KEY_LENGTH]{0};
|
||||
} transfer_key{};
|
||||
|
||||
/* will be used for both directions */
|
||||
struct {
|
||||
std::mutex mutex{};
|
||||
size_t bytes{0};
|
||||
|
||||
bool buffering_stopped{false};
|
||||
|
||||
Buffer* buffer_head{nullptr};
|
||||
Buffer** buffer_tail{&buffer_head};
|
||||
} buffer{};
|
||||
|
||||
struct {
|
||||
sockaddr_storage address{};
|
||||
int file_descriptor{0};
|
||||
|
||||
NetworkingProtocol protocol{PROTOCOL_UNKNOWN};
|
||||
|
||||
struct event* event_read{nullptr};
|
||||
struct event* event_write{nullptr};
|
||||
struct event* event_throttle{nullptr};
|
||||
|
||||
bool add_event_write{false}, add_event_read{false};
|
||||
|
||||
std::chrono::system_clock::time_point disconnect_timeout{};
|
||||
|
||||
NetworkThrottle throttle;
|
||||
|
||||
pipes::SSL pipe_ssl{};
|
||||
bool pipe_ssl_init{false};
|
||||
std::unique_ptr<Buffer, decltype(free_buffer)*> http_header_buffer{nullptr, free_buffer};
|
||||
HTTPUploadState http_state{HTTPUploadState::HTTP_AWAITING_HEADER};
|
||||
|
||||
/* Only read the transfer key length at the beginning. We than have the actual limit which will be set via throttle */
|
||||
size_t max_read_buffer_size{TRANSFER_KEY_LENGTH};
|
||||
} networking{};
|
||||
|
||||
struct {
|
||||
size_t network_bytes_send{0};
|
||||
size_t network_bytes_received{0};
|
||||
|
||||
size_t file_bytes_transferred{0};
|
||||
|
||||
/* used for delta statistics */
|
||||
size_t last_network_bytes_send{0};
|
||||
size_t last_network_bytes_received{0};
|
||||
|
||||
/* used for delta statistics */
|
||||
size_t last_file_bytes_transferred{0};
|
||||
|
||||
size_t disk_bytes_read{0};
|
||||
size_t disk_bytes_write{0};
|
||||
} statistics{};
|
||||
|
||||
struct {
|
||||
std::chrono::system_clock::time_point last_write{};
|
||||
std::chrono::system_clock::time_point last_read{};
|
||||
|
||||
std::chrono::system_clock::time_point connected{};
|
||||
std::chrono::system_clock::time_point key_received{};
|
||||
std::chrono::system_clock::time_point disconnecting{};
|
||||
} timings;
|
||||
|
||||
explicit FileClient(LocalFileTransfer* handle) : handle{handle} {}
|
||||
~FileClient();
|
||||
|
||||
void add_network_write_event(bool /* ignore bandwidth limits */);
|
||||
void add_network_write_event_nolock(bool /* ignore bandwidth limits */);
|
||||
|
||||
/* will check if we've enough space in out read buffer again */
|
||||
void add_network_read_event(bool /* ignore bandwidth limits */);
|
||||
|
||||
bool send_file_bytes(const void* /* buffer */, size_t /* length */);
|
||||
bool enqueue_buffer_bytes(const void* /* buffer */, size_t /* length */);
|
||||
|
||||
[[nodiscard]] inline std::string log_prefix() const { return "[" + net::to_string(this->networking.address) + "]"; }
|
||||
};
|
||||
|
||||
enum struct DiskIOStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct NetworkingStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY,
|
||||
NO_BINDINGS
|
||||
};
|
||||
|
||||
enum struct ClientWorkerStartResult {
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum struct NetworkInitializeResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct FileInitializeResult {
|
||||
SUCCESS,
|
||||
|
||||
INVALID_TRANSFER_DIRECTION,
|
||||
OUT_OF_MEMORY,
|
||||
|
||||
PROCESS_FILE_LIMIT_REACHED,
|
||||
SYSTEM_FILE_LIMIT_REACHED,
|
||||
|
||||
FILE_IS_BUSY,
|
||||
FILE_DOES_NOT_EXISTS,
|
||||
FILE_SYSTEM_ERROR,
|
||||
FILE_IS_A_DIRECTORY,
|
||||
|
||||
FILE_TOO_LARGE,
|
||||
DISK_IS_READ_ONLY,
|
||||
|
||||
FILE_SIZE_MISMATCH,
|
||||
FILE_SEEK_FAILED,
|
||||
|
||||
FILE_IS_NOT_ACCESSIBLE,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
constexpr static std::array<std::string_view, (size_t) FileInitializeResult::MAX> kFileInitializeResultMessages{
|
||||
/* SUCCESS */ "success",
|
||||
|
||||
/* INVALID_TRANSFER_DIRECTION */ "invalid file transfer direction",
|
||||
/* OUT_OF_MEMORY */ "out of memory",
|
||||
|
||||
/* PROCESS_FILE_LIMIT_REACHED */ "process file limit reached",
|
||||
/* SYSTEM_FILE_LIMIT_REACHED */ "system file limit reached",
|
||||
|
||||
/* FILE_IS_BUSY */ "target file is busy",
|
||||
/* FILE_DOES_NOT_EXISTS */ "target file does not exists",
|
||||
/* FILE_SYSTEM_ERROR */ "internal file system error",
|
||||
/* FILE_IS_A_DIRECTORY */ "target file is a directory",
|
||||
|
||||
/* FILE_TOO_LARGE */ "file is too large",
|
||||
/* DISK_IS_READ_ONLY */ "disk is in read only mode",
|
||||
|
||||
/* FILE_SIZE_MISMATCH */ "file size mismatch",
|
||||
/* FILE_SEEK_FAILED */ "failed to seek to target file offset",
|
||||
|
||||
/* FILE_IS_NOT_ACCESSIBLE */ "file is not accessible"
|
||||
};
|
||||
|
||||
enum struct TransferKeyApplyResult {
|
||||
SUCCESS,
|
||||
FILE_ERROR,
|
||||
UNKNOWN_KEY,
|
||||
|
||||
INTERNAL_ERROR
|
||||
};
|
||||
|
||||
enum struct TransferUploadRawResult {
|
||||
MORE_DATA_TO_RECEIVE,
|
||||
FINISH,
|
||||
|
||||
/* UNKNOWN ERROR */
|
||||
};
|
||||
|
||||
enum struct TransferUploadHTTPResult {
|
||||
MORE_DATA_TO_RECEIVE,
|
||||
FINISH,
|
||||
|
||||
BOUNDARY_MISSING,
|
||||
BOUNDARY_TOKEN_INVALID,
|
||||
BOUNDARY_INVALID,
|
||||
|
||||
MISSING_CONTENT_TYPE,
|
||||
INVALID_CONTENT_TYPE
|
||||
/* UNKNOWN ERROR */
|
||||
};
|
||||
|
||||
struct NetworkBinding : std::enable_shared_from_this<NetworkBinding> {
|
||||
std::string hostname{};
|
||||
sockaddr_storage address{};
|
||||
|
||||
int file_descriptor{-1};
|
||||
struct event* accept_event{nullptr};
|
||||
|
||||
LocalFileTransfer* handle{nullptr};
|
||||
};
|
||||
|
||||
class LocalFileTransfer : public AbstractProvider {
|
||||
public:
|
||||
explicit LocalFileTransfer(filesystem::LocalFileSystem&);
|
||||
~LocalFileTransfer();
|
||||
|
||||
[[nodiscard]] bool start(const std::deque<std::shared_ptr<NetworkBinding>>& /* bindings */, const std::shared_ptr<pipes::SSL::Options>& /* ssl options */);
|
||||
void stop();
|
||||
|
||||
[[nodiscard]] inline const auto& ssl_options() const {
|
||||
return this->ssl_options_;
|
||||
}
|
||||
|
||||
inline void set_ssl_options(const std::shared_ptr<pipes::SSL::Options>& options) {
|
||||
this->ssl_options_ = options;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_channel_transfer(Transfer::Direction direction, ServerId id, ChannelId channelId,
|
||||
const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_icon_transfer(Transfer::Direction direction, ServerId id, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_avatar_transfer(Transfer::Direction direction, ServerId id, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(transfer_id id, bool) override;
|
||||
private:
|
||||
enum struct DiskIOLoopState {
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
|
||||
STOPPING,
|
||||
FORCE_STOPPING
|
||||
};
|
||||
filesystem::LocalFileSystem& file_system_;
|
||||
|
||||
std::atomic<transfer_id> current_transfer_id{0};
|
||||
std::mt19937 transfer_random_token_generator{std::random_device{}()};
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::mutex transfers_mutex{};
|
||||
std::deque<std::shared_ptr<FileClient>> transfers_{};
|
||||
std::deque<std::shared_ptr<Transfer>> pending_transfers{};
|
||||
|
||||
std::shared_ptr<pipes::SSL::Options> ssl_options_{};
|
||||
|
||||
enum ServerState {
|
||||
STOPPED,
|
||||
RUNNING
|
||||
} state{ServerState::STOPPED};
|
||||
|
||||
struct {
|
||||
bool active{false};
|
||||
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex mutex{};
|
||||
std::condition_variable notify_cv{};
|
||||
} disconnect;
|
||||
|
||||
struct {
|
||||
bool active{false};
|
||||
std::thread dispatch_thread{};
|
||||
struct event_base* event_base{nullptr};
|
||||
|
||||
std::deque<std::shared_ptr<NetworkBinding>> bindings{};
|
||||
} network{};
|
||||
|
||||
struct {
|
||||
DiskIOLoopState state{DiskIOLoopState::STOPPED};
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex queue_lock{};
|
||||
std::condition_variable notify_work_awaiting{};
|
||||
std::condition_variable notify_client_processed{};
|
||||
|
||||
FileClient* queue_head{nullptr};
|
||||
FileClient** queue_tail{&queue_head};
|
||||
} disk_io{};
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_transfer(Transfer::Direction, ServerId, ChannelId, Transfer::TargetType, const TransferInfo &info);
|
||||
|
||||
[[nodiscard]] NetworkingStartResult start_networking();
|
||||
[[nodiscard]] DiskIOStartResult start_disk_io();
|
||||
[[nodiscard]] ClientWorkerStartResult start_client_worker();
|
||||
|
||||
void shutdown_networking();
|
||||
void shutdown_disk_io();
|
||||
void shutdown_client_worker();
|
||||
|
||||
void disconnect_client(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */, bool /* flush */);
|
||||
|
||||
[[nodiscard]] NetworkInitializeResult initialize_networking(const std::shared_ptr<FileClient>& /* client */, int /* file descriptor */);
|
||||
/* might block 'till all IO operations have been succeeded */
|
||||
void finalize_networking(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
[[nodiscard]] FileInitializeResult initialize_file_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void finalize_file_io(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
[[nodiscard]] bool initialize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
|
||||
void finalize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
void enqueue_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void execute_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
void report_transfer_statistics(const std::shared_ptr<FileClient>& /* client */);
|
||||
[[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */);
|
||||
[[nodiscard]] TransferUploadHTTPResult handle_transfer_upload_http(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */);
|
||||
|
||||
void send_http_response(const std::shared_ptr<FileClient>& /* client */, http::HttpResponse& /* response */);
|
||||
|
||||
static void callback_transfer_network_write(int, short, void*);
|
||||
static void callback_transfer_network_read(int, short, void*);
|
||||
static void callback_transfer_network_throttle(int, short, void*);
|
||||
static void callback_transfer_network_accept(int, short, void*);
|
||||
|
||||
static void dispatch_loop_client_worker(void*);
|
||||
static void dispatch_loop_network(void*);
|
||||
static void dispatch_loop_disk_io(void*);
|
||||
|
||||
size_t handle_transfer_read(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
|
||||
size_t handle_transfer_read_raw(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
|
||||
[[nodiscard]] TransferKeyApplyResult handle_transfer_key_provided(const std::shared_ptr<FileClient>& /* client */, std::string& /* error */);
|
||||
};
|
||||
}
|
||||
networking::NetworkThrottle upload_throttle{};
|
||||
networking::NetworkThrottle download_throttle{};
|
||||
};
|
||||
|
||||
class LocalFileProvider : public AbstractFileServer {
|
||||
public:
|
||||
LocalFileProvider();
|
||||
virtual ~LocalFileProvider();
|
||||
|
||||
[[nodiscard]] bool initialize(std::string& /* error */, const std::shared_ptr<pipes::SSL::Options>& /* ssl options */);
|
||||
[[nodiscard]] bool initialize(std::string& /* error */);
|
||||
void finalize();
|
||||
|
||||
[[nodiscard]] std::string file_base_path() const override;
|
||||
|
||||
filesystem::AbstractProvider &file_system() override;
|
||||
transfer::AbstractProvider &file_transfer() override;
|
||||
|
||||
|
||||
std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) override;
|
||||
void unregister_server(ServerId /* server id */) override;
|
||||
private:
|
||||
filesystem::LocalFileSystem file_system_;
|
||||
transfer::LocalFileTransfer file_transfer_;
|
||||
filesystem::LocalFileSystem* file_system_;
|
||||
transfer::LocalFileTransfer* file_transfer_;
|
||||
};
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
#define FS_INCLUDED
|
||||
|
||||
#include <log/LogUtils.h>
|
||||
#include "LocalFileProvider.h"
|
||||
#include "clnpath.h"
|
||||
#include "./LocalFileSystem.h"
|
||||
#include "./clnpath.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::filesystem;
|
||||
@@ -36,12 +36,12 @@ bool LocalFileSystem::initialize(std::string &error_message, const std::string &
|
||||
|
||||
void LocalFileSystem::finalize() {}
|
||||
|
||||
fs::path LocalFileSystem::server_path(ts::ServerId id) {
|
||||
return fs::u8path(this->root_path_) / fs::u8path("server_" + std::to_string(id));
|
||||
fs::path LocalFileSystem::server_path(const std::shared_ptr<VirtualFileServer> &server) {
|
||||
return fs::u8path(this->root_path_) / fs::u8path("server_" + std::to_string(server->server_id()));
|
||||
}
|
||||
|
||||
fs::path LocalFileSystem::server_channel_path(ts::ServerId sid, ts::ChannelId cid) {
|
||||
return this->server_path(sid) / fs::u8path("channel_" + std::to_string(cid));
|
||||
fs::path LocalFileSystem::server_channel_path(const std::shared_ptr<VirtualFileServer> &server, ts::ChannelId cid) {
|
||||
return this->server_path(server) / fs::u8path("channel_" + std::to_string(cid));
|
||||
}
|
||||
|
||||
bool LocalFileSystem::exceeds_base_path(const fs::path &base, const fs::path &target) {
|
||||
@@ -67,32 +67,32 @@ bool LocalFileSystem::is_any_file_locked(const fs::path &base, const std::string
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::target_file_path(FileCategory type, ts::ServerId sid, ts::ChannelId cid, const std::string &path) {
|
||||
std::string LocalFileSystem::target_file_path(FileCategory type, const std::shared_ptr<VirtualFileServer> &server, ts::ChannelId cid, const std::string &path) {
|
||||
fs::path target_path{};
|
||||
switch (type) {
|
||||
case FileCategory::CHANNEL:
|
||||
target_path = this->server_channel_path(sid, cid) / path;
|
||||
target_path = this->server_channel_path(server, cid) / path;
|
||||
break;
|
||||
case FileCategory::ICON:
|
||||
target_path = this->server_path(sid) / "icons" / path;
|
||||
target_path = this->server_path(server) / "icons" / path;
|
||||
break;
|
||||
case FileCategory::AVATAR:
|
||||
target_path = this->server_path(sid) / "avatars" / path;
|
||||
target_path = this->server_path(server) / "avatars" / path;
|
||||
break;
|
||||
}
|
||||
|
||||
return clnpath(fs::absolute(target_path).string());
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_avatar_path(ServerId sid, const std::string &path) {
|
||||
std::string LocalFileSystem::absolute_avatar_path(const std::shared_ptr<VirtualFileServer> &sid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::AVATAR, sid, 0, path);
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_icon_path(ServerId sid, const std::string &path) {
|
||||
std::string LocalFileSystem::absolute_icon_path(const std::shared_ptr<VirtualFileServer> &sid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::ICON, sid, 0, path);
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_channel_path(ServerId sid, ChannelId cid, const std::string &path) {
|
||||
std::string LocalFileSystem::absolute_channel_path(const std::shared_ptr<VirtualFileServer> &sid, ChannelId cid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::CHANNEL, sid, cid, path);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ void LocalFileSystem::unlock_file(const std::string &c_path) {
|
||||
this->locked_files_.erase(std::remove_if(this->locked_files_.begin(), this->locked_files_.end(), [&](const auto& p) { return p == c_path; }), this->locked_files_.end());
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::initialize_server(ServerId id) {
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::initialize_server(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
auto path = this->server_path(id);
|
||||
std::error_code error{};
|
||||
|
||||
@@ -126,7 +126,7 @@ std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::initialize
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::delete_server(ServerId id) {
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::delete_server(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
auto path = this->server_path(id);
|
||||
std::error_code error{};
|
||||
|
||||
@@ -190,6 +190,10 @@ std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(con
|
||||
dentry.type = DirectoryEntry::DIRECTORY;
|
||||
dentry.name = entry.path().filename();
|
||||
|
||||
dentry.empty = fs::is_empty(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message());
|
||||
|
||||
dentry.modified_at = fs::last_write_time(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", entry.path().string(), error.value(), error.message());
|
||||
@@ -217,19 +221,19 @@ std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(con
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_icon_directory(ServerId id) {
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_icon_directory(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
return this->query_directory(this->server_path(id) / fs::u8path("icons"), "/", true);
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_avatar_directory(ServerId id) {
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_avatar_directory(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
return this->query_directory(this->server_path(id) / fs::u8path("avatars"), "/", true);
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_channel_directory(ServerId id, ChannelId channelId, const std::string &path) {
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_channel_directory(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string &path) {
|
||||
return this->query_directory(this->server_channel_path(id, channelId), path, false);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>> LocalFileSystem::create_channel_directory(ServerId id, ChannelId channelId, const std::string &path) {
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>> LocalFileSystem::create_channel_directory(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string &path) {
|
||||
auto channel_path_root = this->server_channel_path(id, channelId);
|
||||
std::error_code error{};
|
||||
|
||||
@@ -256,20 +260,21 @@ std::shared_ptr<ExecuteResponse<DirectoryModifyError>> LocalFileSystem::create_c
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channel_file(ServerId id, ChannelId channelId, const std::string ¤t_path_string, const std::string &new_path_string) {
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channel_file(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string ¤t_path_string, ChannelId targetChannelId, const std::string &new_path_string) {
|
||||
auto channel_path_root = this->server_channel_path(id, channelId);
|
||||
auto target_path_root = this->server_channel_path(id, targetChannelId);
|
||||
std::error_code error{};
|
||||
std::string locked_file{};
|
||||
|
||||
auto response = this->create_execute_response<FileModifyError>();
|
||||
auto current_path = channel_path_root / fs::u8path(current_path_string);
|
||||
auto target_path = channel_path_root / fs::u8path(new_path_string);
|
||||
auto target_path = target_path_root / fs::u8path(new_path_string);
|
||||
|
||||
if(this->exceeds_base_path(channel_path_root, current_path)) {
|
||||
response->emplace_fail(FileModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
if(this->exceeds_base_path(channel_path_root, target_path)) {
|
||||
if(this->exceeds_base_path(target_path_root, target_path)) {
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
@@ -283,6 +288,15 @@ std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channe
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::exists(target_path.parent_path(), error)) {
|
||||
if(!fs::create_directories(target_path.parent_path(), error)) {
|
||||
response->emplace_fail(FileModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to test for target directory existence for {}: {}/{}. Assuming it does exists", target_path.parent_path().string(), error.value(), error.message());
|
||||
}
|
||||
|
||||
if(fs::exists(target_path, error)) {
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
@@ -307,44 +321,137 @@ std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channe
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::delete_file(const fs::path &base,
|
||||
const std::string &path) {
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_files(const fs::path &base,
|
||||
const std::vector<std::string> &paths) {
|
||||
std::error_code error{};
|
||||
std::string locked_file{};
|
||||
auto response = this->create_execute_response<FileModifyError>();
|
||||
auto target_path = base / fs::u8path(path);
|
||||
auto response = this->create_execute_response<FileDeleteError, FileDeleteResponse>();
|
||||
|
||||
if(fs::exists(target_path, error)) {
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", target_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
std::vector<FileDeleteResponse::DeleteResult> delete_results{};
|
||||
for(const auto& path : paths) {
|
||||
auto target_path = base / fs::u8path(path);
|
||||
|
||||
if(!fs::exists(target_path, error)) {
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS, "");
|
||||
continue;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", target_path.string(), error.value(), error.message());
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this->is_any_file_locked(base, path, locked_file)) {
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::SOME_FILES_ARE_LOCKED, locked_file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!fs::remove_all(target_path, error) || error) {
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES, std::to_string(error.value()) + "/" + error.message());
|
||||
continue;
|
||||
}
|
||||
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::SUCCESS, "");
|
||||
}
|
||||
|
||||
if(this->is_any_file_locked(base, path, locked_file)) {
|
||||
response->emplace_fail(FileModifyErrorType::SOME_FILES_ARE_LOCKED, locked_file);
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::remove(target_path, error) || error) {
|
||||
response->emplace_fail(FileModifyErrorType::FAILED_TO_DELETE_FILES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
response->emplace_success(FileDeleteResponse{delete_results});
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::delete_channel_file(ServerId id, ChannelId channelId, const std::string &path) {
|
||||
return this->delete_file(this->server_channel_path(id, channelId), path);
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_channel_files(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::vector<std::string> &path) {
|
||||
return this->delete_files(this->server_channel_path(id, channelId), path);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::delete_icon(ServerId id, const std::string &icon) {
|
||||
return this->delete_file(this->server_path(id) / fs::u8path("icons"), icon);
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_icons(const std::shared_ptr<VirtualFileServer> &id, const std::vector<std::string> &icon) {
|
||||
return this->delete_files(this->server_path(id) / fs::u8path("icons"), icon);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::delete_avatar(ServerId id, const std::string &avatar) {
|
||||
return this->delete_file(this->server_path(id) / fs::u8path("avatars"), avatar);
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_avatars(const std::shared_ptr<VirtualFileServer> &id, const std::vector<std::string> &avatar) {
|
||||
return this->delete_files(this->server_path(id) / fs::u8path("avatars"), avatar);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_file_info(const std::vector<std::tuple<fs::path, std::string>> &paths) {
|
||||
std::error_code error{};
|
||||
auto response = this->create_execute_response<FileInfoError, FileInfoResponse>();
|
||||
std::vector<FileInfoResponse::FileInfo> file_infos{};
|
||||
file_infos.reserve(paths.size());
|
||||
|
||||
for(const auto& [base, path] : paths) {
|
||||
auto target_path = base / fs::u8path(path);
|
||||
if(this->exceeds_base_path(base, target_path)) {
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_EXCEEDS_ROOT_PATH, "", DirectoryEntry{});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!fs::exists(target_path, error)) {
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS, "", DirectoryEntry{});
|
||||
continue;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS, "", DirectoryEntry{});
|
||||
continue;
|
||||
}
|
||||
|
||||
auto status = fs::status(target_path, error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to query file status for {} ({}/{}). Skipping entry for file info query.", target_path.string(), error.value(), error.message());
|
||||
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::FAILED_TO_QUERY_INFO, "", DirectoryEntry{});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(status.type() == fs::file_type::directory) {
|
||||
DirectoryEntry dentry{};
|
||||
dentry.type = DirectoryEntry::DIRECTORY;
|
||||
dentry.name = target_path.filename();
|
||||
dentry.empty = fs::is_empty(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message());
|
||||
|
||||
dentry.modified_at = fs::last_write_time(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", target_path.string(), error.value(), error.message());
|
||||
dentry.size = 0;
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::SUCCESS, "", dentry);
|
||||
} else if(status.type() == fs::file_type::regular) {
|
||||
DirectoryEntry dentry{};
|
||||
dentry.type = DirectoryEntry::FILE;
|
||||
dentry.name = target_path.filename();
|
||||
|
||||
dentry.modified_at = fs::last_write_time(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{}).", target_path.string(), error.value(), error.message());
|
||||
dentry.size = fs::file_size(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query size for file {} ({}/{}).", target_path.string(), error.value(), error.message());
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::SUCCESS, "", dentry);
|
||||
} else {
|
||||
logWarning(LOG_FT, "File info query contains an unknown file type for file {} ({}).", target_path.string(), (int) status.type());
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::UNKNOWN_FILE_TYPE, "", DirectoryEntry{});
|
||||
}
|
||||
}
|
||||
|
||||
response->emplace_success(FileInfoResponse{file_infos});
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_channel_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::tuple<ChannelId, std::string>>& files) {
|
||||
std::vector<std::tuple<fs::path, std::string>> file_paths{};
|
||||
for(const auto& [channelId, path] : files)
|
||||
file_paths.emplace_back(this->server_channel_path(sid, channelId), path);
|
||||
return this->query_file_info(file_paths);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_icon_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
|
||||
std::vector<std::tuple<fs::path, std::string>> file_paths{};
|
||||
for(const auto& path : paths)
|
||||
file_paths.emplace_back(this->server_path(sid) / fs::u8path("icons"), path);
|
||||
return this->query_file_info(file_paths);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse> > LocalFileSystem::query_avatar_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
|
||||
std::vector<std::tuple<fs::path, std::string>> file_paths{};
|
||||
for(const auto& path : paths)
|
||||
file_paths.emplace_back(this->server_path(sid) / fs::u8path("avatars"), path);
|
||||
return this->query_file_info(file_paths);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Created by WolverinDEV on 16/09/2020.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <sys/socket.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <random>
|
||||
#include <misc/memtracker.h>
|
||||
#include "./NetTools.h"
|
||||
|
||||
namespace ts::server::file::filesystem {
|
||||
#ifdef FS_INCLUDED
|
||||
namespace fs = std::experimental::filesystem;
|
||||
#endif
|
||||
|
||||
class LocalFileSystem : public filesystem::AbstractProvider {
|
||||
using FileModifyError = filesystem::FileModifyError;
|
||||
using DirectoryModifyError = filesystem::DirectoryModifyError;
|
||||
public:
|
||||
enum struct FileCategory {
|
||||
ICON,
|
||||
AVATAR,
|
||||
CHANNEL
|
||||
};
|
||||
|
||||
virtual ~LocalFileSystem();
|
||||
|
||||
bool initialize(std::string & /* error */, const std::string & /* root path */);
|
||||
void finalize();
|
||||
|
||||
void lock_file(const std::string& /* absolute path */);
|
||||
void unlock_file(const std::string& /* absolute path */);
|
||||
|
||||
[[nodiscard]] inline const auto &root_path() const { return this->root_path_; }
|
||||
|
||||
[[nodiscard]] std::string absolute_avatar_path(const std::shared_ptr<VirtualFileServer> &, const std::string&);
|
||||
[[nodiscard]] std::string absolute_icon_path(const std::shared_ptr<VirtualFileServer> &, const std::string&);
|
||||
[[nodiscard]] std::string absolute_channel_path(const std::shared_ptr<VirtualFileServer> &, ChannelId, const std::string&);
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> & /* server */) override;
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(const std::shared_ptr<VirtualFileServer> & /* server */) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_channel_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t>
|
||||
query_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
|
||||
create_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_channel_files(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::vector<std::string> &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
rename_channel_file(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &, ChannelId, const std::string &) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_icon_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> & id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_icons(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_avatar_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> & id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_avatars(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
|
||||
|
||||
private:
|
||||
#ifdef FS_INCLUDED
|
||||
[[nodiscard]] fs::path server_path(const std::shared_ptr<VirtualFileServer> &);
|
||||
[[nodiscard]] fs::path server_channel_path(const std::shared_ptr<VirtualFileServer> &, ChannelId);
|
||||
[[nodiscard]] static bool exceeds_base_path(const fs::path& /* base */, const fs::path& /* target */);
|
||||
[[nodiscard]] bool is_any_file_locked(const fs::path& /* base */, const std::string& /* path */, std::string& /* file (relative to the base) */);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_files(const fs::path& /* base */, const std::vector<std::string> &string);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<directory_query_response_t>
|
||||
query_directory(const fs::path& /* base */, const std::string &string, bool);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_file_info(const std::vector<std::tuple<fs::path, std::string>> &string);
|
||||
#endif
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
std::string target_file_path(FileCategory type, const std::shared_ptr<VirtualFileServer> &sid, ts::ChannelId cid, const std::string &path);
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::string root_path_{};
|
||||
|
||||
std::mutex locked_files_mutex{};
|
||||
std::deque<std::string> locked_files_{};
|
||||
};
|
||||
}
|
||||
@@ -7,8 +7,10 @@
|
||||
#include <log/LogUtils.h>
|
||||
#include <random>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "LocalFileProvider.h"
|
||||
#include "./LocalFileTransfer.h"
|
||||
#include <experimental/filesystem>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
|
||||
@@ -26,12 +28,11 @@ void transfer::free_buffer(Buffer* buffer) {
|
||||
}
|
||||
|
||||
FileClient::~FileClient() {
|
||||
auto head = this->buffer.buffer_head;
|
||||
while (head) {
|
||||
auto next = head->next;
|
||||
free_buffer(head);
|
||||
head = next;
|
||||
}
|
||||
this->flush_network_buffer();
|
||||
this->flush_disk_buffer();
|
||||
|
||||
assert(!this->disk_buffer.buffer_head);
|
||||
assert(!this->network_buffer.buffer_head);
|
||||
|
||||
assert(!this->file.file_descriptor);
|
||||
assert(!this->file.currently_processing);
|
||||
@@ -41,15 +42,13 @@ FileClient::~FileClient() {
|
||||
assert(!this->networking.event_write);
|
||||
|
||||
assert(this->state == STATE_DISCONNECTED);
|
||||
memtrack::freed<FileClient>(this);
|
||||
}
|
||||
|
||||
LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem &fs) : file_system_{fs} {}
|
||||
LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem *fs) : file_system_{fs} {}
|
||||
LocalFileTransfer::~LocalFileTransfer() = default;
|
||||
|
||||
bool LocalFileTransfer::start(const std::deque<std::shared_ptr<NetworkBinding>>& bindings, const std::shared_ptr<pipes::SSL::Options>& ssl_options) {
|
||||
assert(ssl_options);
|
||||
this->ssl_options_ = ssl_options;
|
||||
|
||||
bool LocalFileTransfer::start() {
|
||||
(void) this->start_client_worker();
|
||||
|
||||
{
|
||||
@@ -68,17 +67,15 @@ bool LocalFileTransfer::start(const std::deque<std::shared_ptr<NetworkBinding>>&
|
||||
|
||||
|
||||
{
|
||||
this->network.bindings = bindings;
|
||||
auto start_result = this->start_networking();
|
||||
switch (start_result) {
|
||||
case NetworkingStartResult::SUCCESS:
|
||||
break;
|
||||
|
||||
case NetworkingStartResult::OUT_OF_MEMORY:
|
||||
logError(LOG_FT, "Failed to start networking (Out of memory)");
|
||||
goto error_exit_network;
|
||||
case NetworkingStartResult::NO_BINDINGS:
|
||||
logError(LOG_FT, "Failed to start networking (No address could be bound)");
|
||||
goto error_exit_network;
|
||||
|
||||
default:
|
||||
logError(LOG_FT, "Failed to start networking ({})", (int) start_result);
|
||||
goto error_exit_network;
|
||||
@@ -101,29 +98,59 @@ void LocalFileTransfer::stop() {
|
||||
this->shutdown_client_worker();
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_icon_transfer(Transfer::Direction direction, ServerId sid, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, sid, 0, Transfer::TARGET_TYPE_ICON, info);
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
LocalFileTransfer::initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, server, 0, Transfer::TARGET_TYPE_ICON, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_avatar_transfer(Transfer::Direction direction, ServerId sid, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, sid, 0, Transfer::TARGET_TYPE_AVATAR, info);
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
LocalFileTransfer::initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, server, 0, Transfer::TARGET_TYPE_AVATAR, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_channel_transfer(Transfer::Direction direction, ServerId sid, ChannelId cid, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, sid, cid, Transfer::TARGET_TYPE_CHANNEL_FILE, info);
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
LocalFileTransfer::initialize_channel_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, ChannelId cid, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, server, cid, Transfer::TARGET_TYPE_CHANNEL_FILE, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_transfer(
|
||||
Transfer::Direction direction, ServerId sid, ChannelId cid,
|
||||
Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, ChannelId cid,
|
||||
Transfer::TargetType ttype,
|
||||
const TransferInfo &info) {
|
||||
auto response = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
|
||||
|
||||
/* TODO: test for a transfer limit */
|
||||
std::lock_guard clock{this->transfer_create_mutex};
|
||||
if(info.max_concurrent_transfers > 0) {
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
{
|
||||
auto transfers = std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& client) {
|
||||
return client->transfer && client->transfer->client_unique_id == info.client_unique_id && client->state < FileClient::STATE_FLUSHING;
|
||||
});
|
||||
transfers += std::count_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr<Transfer>& transfer) {
|
||||
return transfer->client_unique_id == info.client_unique_id;
|
||||
});
|
||||
|
||||
if(transfers >= info.max_concurrent_transfers) {
|
||||
response->emplace_fail(TransferInitError::CLIENT_TOO_MANY_TRANSFERS, std::to_string(transfers));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto server_transfers = this->pending_transfers.size();
|
||||
server_transfers += std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& client) {
|
||||
return client->transfer;
|
||||
});
|
||||
if(server_transfers >= this->max_concurrent_transfers) {
|
||||
response->emplace_fail(TransferInitError::SERVER_TOO_MANY_TRANSFERS, std::to_string(server_transfers));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto transfer = std::make_shared<Transfer>();
|
||||
transfer->server_transfer_id = ++this->current_transfer_id;
|
||||
transfer->server_id = sid;
|
||||
transfer->server_transfer_id = server->generate_transfer_id();
|
||||
transfer->server = server;
|
||||
transfer->channel_id = cid;
|
||||
transfer->target_type = ttype;
|
||||
transfer->direction = direction;
|
||||
@@ -142,6 +169,8 @@ std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> L
|
||||
transfer->file_offset = info.file_offset;
|
||||
transfer->expected_file_size = info.expected_file_size;
|
||||
transfer->max_bandwidth = info.max_bandwidth;
|
||||
transfer->client_unique_id = info.client_unique_id;
|
||||
transfer->client_id = info.client_id;
|
||||
|
||||
constexpr static std::string_view kTokenCharacters{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"};
|
||||
for(auto& c : transfer->transfer_key)
|
||||
@@ -152,11 +181,97 @@ std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> L
|
||||
|
||||
transfer->initialized_timestamp = std::chrono::system_clock::now();
|
||||
|
||||
{
|
||||
std::string absolute_path{};
|
||||
switch (transfer->target_type) {
|
||||
case Transfer::TARGET_TYPE_AVATAR:
|
||||
absolute_path = this->file_system_->absolute_avatar_path(transfer->server, transfer->target_file_path);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_ICON:
|
||||
absolute_path = this->file_system_->absolute_icon_path(transfer->server, transfer->target_file_path);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_CHANNEL_FILE:
|
||||
absolute_path = this->file_system_->absolute_channel_path(transfer->server, transfer->channel_id, transfer->target_file_path);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_UNKNOWN:
|
||||
default:
|
||||
response->emplace_fail(TransferInitError::INVALID_FILE_TYPE, "");
|
||||
return response;
|
||||
}
|
||||
transfer->absolute_file_path = absolute_path;
|
||||
|
||||
const auto root_path_length = this->file_system_->root_path().size();
|
||||
if(root_path_length < absolute_path.size())
|
||||
transfer->relative_file_path = absolute_path.substr(root_path_length);
|
||||
else
|
||||
transfer->relative_file_path = "error";
|
||||
transfer->file_name = fs::u8path(absolute_path).filename();
|
||||
}
|
||||
|
||||
if(direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
auto path = fs::u8path(transfer->absolute_file_path);
|
||||
std::error_code error{};
|
||||
if(!fs::exists(path, error)) {
|
||||
response->emplace_fail(TransferInitError::FILE_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", transfer->absolute_file_path, error.value(), error.message());
|
||||
response->emplace_fail(TransferInitError::FILE_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
auto status = fs::status(path, error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to status for file at {}: {}. Ignoring file transfer.", transfer->absolute_file_path, error.value(), error.message());
|
||||
response->emplace_fail(TransferInitError::IO_ERROR, "stat");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(status.type() != fs::file_type::regular) {
|
||||
response->emplace_fail(TransferInitError::FILE_IS_NOT_A_FILE, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
transfer->expected_file_size = fs::file_size(path, error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to get file size for file at {}: {}. Ignoring file transfer.", transfer->absolute_file_path, error.value(), error.message());
|
||||
response->emplace_fail(TransferInitError::IO_ERROR, "file_size");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(info.download_client_quota_limit > 0 && info.download_client_quota_limit <= transfer->expected_file_size) {
|
||||
response->emplace_fail(TransferInitError::CLIENT_QUOTA_EXCEEDED, "");
|
||||
return response;
|
||||
}
|
||||
if(info.download_server_quota_limit > 0 && info.download_server_quota_limit <= transfer->expected_file_size) {
|
||||
response->emplace_fail(TransferInitError::SERVER_QUOTA_EXCEEDED, "");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard tlock{this->transfers_mutex};
|
||||
this->pending_transfers.push_back(transfer);
|
||||
}
|
||||
|
||||
switch (transfer->target_type) {
|
||||
case Transfer::TARGET_TYPE_AVATAR:
|
||||
logMessage(LOG_FT, "Initialized avatar transfer for avatar \"{}\" ({} bytes, transferring {} bytes).", transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_ICON:
|
||||
logMessage(LOG_FT, "Initialized icon transfer for icon \"{}\" ({} bytes, transferring {} bytes).",
|
||||
transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_CHANNEL_FILE:
|
||||
logMessage(LOG_FT, "Initialized channel transfer for file \"{}/{}\" ({} bytes, transferring {} bytes).",
|
||||
transfer->channel_id, transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_UNKNOWN:
|
||||
default:
|
||||
response->emplace_fail(TransferInitError::INVALID_FILE_TYPE, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_transfer_registered}; callback)
|
||||
callback(transfer);
|
||||
|
||||
@@ -164,7 +279,7 @@ std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> L
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_transfer(transfer_id id, bool flush) {
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_transfer(const std::shared_ptr<VirtualFileServer>& server, transfer_id id, bool flush) {
|
||||
auto response = this->create_execute_response<TransferActionError>();
|
||||
|
||||
std::shared_ptr<Transfer> transfer{};
|
||||
@@ -174,13 +289,13 @@ std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_tr
|
||||
std::lock_guard tlock{this->transfers_mutex};
|
||||
|
||||
auto ct_it = std::find_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& t) {
|
||||
return t->transfer && t->transfer->server_transfer_id == id;
|
||||
return t->transfer && t->transfer->server_transfer_id == id && t->transfer->server == server;
|
||||
});
|
||||
if(ct_it != this->transfers_.end())
|
||||
connected_transfer = *ct_it;
|
||||
else {
|
||||
auto t_it = std::find_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr<Transfer>& t) {
|
||||
return t->server_transfer_id == id;
|
||||
return t->server_transfer_id == id && t->server == server;
|
||||
});
|
||||
if(t_it != this->pending_transfers.end()) {
|
||||
transfer = *t_it;
|
||||
@@ -199,14 +314,72 @@ std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_tr
|
||||
}
|
||||
|
||||
if(connected_transfer) {
|
||||
this->invoke_aborted_callback(connected_transfer, { TransferError::USER_REQUEST, "" });
|
||||
logMessage(LOG_FT, "{} Stopping transfer due to an user request.", connected_transfer->log_prefix());
|
||||
|
||||
std::unique_lock slock{connected_transfer->state_mutex};
|
||||
this->disconnect_client(connected_transfer, slock, flush);
|
||||
} else {
|
||||
this->invoke_aborted_callback(transfer, { TransferError::USER_REQUEST, "" });
|
||||
logMessage(LOG_FT, "Removing pending file transfer for id {}", id);
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
inline void apply_transfer_info(const std::shared_ptr<Transfer>& transfer, ActiveFileTransfer& info) {
|
||||
info.server_transfer_id = transfer->server_transfer_id;
|
||||
info.client_transfer_id = transfer->client_transfer_id;
|
||||
info.direction = transfer->direction;
|
||||
info.client_id = transfer->client_id;
|
||||
info.client_unique_id = transfer->client_unique_id;
|
||||
|
||||
info.file_path = transfer->relative_file_path;
|
||||
info.file_name = transfer->file_name;
|
||||
|
||||
info.expected_size = transfer->expected_file_size;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> LocalFileTransfer::list_transfer() {
|
||||
std::vector<ActiveFileTransfer> transfer_infos{};
|
||||
auto response = this->create_execute_response<TransferListError, std::vector<ActiveFileTransfer>>();
|
||||
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
auto awaiting_transfers = this->pending_transfers;
|
||||
auto running_transfers = this->transfers_;
|
||||
tlock.unlock();
|
||||
|
||||
transfer_infos.reserve(awaiting_transfers.size() + running_transfers.size());
|
||||
for(const auto& transfer : awaiting_transfers) {
|
||||
ActiveFileTransfer info{};
|
||||
apply_transfer_info(transfer, info);
|
||||
info.size_done = transfer->file_offset;
|
||||
|
||||
info.status = ActiveFileTransfer::NOT_STARTED;
|
||||
info.runtime = std::chrono::milliseconds{0};
|
||||
|
||||
info.average_speed = 0;
|
||||
info.current_speed = 0;
|
||||
transfer_infos.push_back(info);
|
||||
}
|
||||
|
||||
for(const auto& client : running_transfers) {
|
||||
auto transfer = client->transfer;
|
||||
if(!transfer) continue;
|
||||
|
||||
ActiveFileTransfer info{};
|
||||
apply_transfer_info(transfer, info);
|
||||
info.size_done = transfer->file_offset + client->statistics.file_transferred.total_bytes;
|
||||
|
||||
info.status = ActiveFileTransfer::RUNNING;
|
||||
info.runtime = std::chrono::floor<std::chrono::milliseconds>(std::chrono::system_clock::now() - client->timings.key_received);
|
||||
|
||||
info.average_speed = client->statistics.file_transferred.average_bandwidth();
|
||||
info.current_speed = client->statistics.file_transferred.current_bandwidth();
|
||||
transfer_infos.push_back(info);
|
||||
}
|
||||
|
||||
response->emplace_success(std::move(transfer_infos));
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
//
|
||||
// Created by WolverinDEV on 16/09/2020.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <sys/socket.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <random>
|
||||
#include <misc/memtracker.h>
|
||||
#include "./NetTools.h"
|
||||
#include "LocalFileSystem.h"
|
||||
|
||||
#define TRANSFER_MAX_CACHED_BYTES (1024 * 1024 * 1) // Buffer up to 1mb
|
||||
|
||||
namespace ts::server::file::transfer {
|
||||
class LocalFileTransfer;
|
||||
|
||||
struct Buffer {
|
||||
Buffer* next{nullptr};
|
||||
|
||||
size_t capacity{0};
|
||||
size_t length{0};
|
||||
size_t offset{0};
|
||||
|
||||
char data[1]{};
|
||||
};
|
||||
[[nodiscard]] extern Buffer* allocate_buffer(size_t);
|
||||
extern void free_buffer(Buffer*);
|
||||
|
||||
/* all variables are locked via the state_mutex */
|
||||
struct FileClient : std::enable_shared_from_this<FileClient> {
|
||||
LocalFileTransfer* handle;
|
||||
std::shared_ptr<Transfer> transfer{nullptr};
|
||||
|
||||
std::shared_mutex state_mutex{};
|
||||
enum {
|
||||
STATE_AWAITING_KEY, /* includes SSL/HTTP init */
|
||||
STATE_TRANSFERRING,
|
||||
STATE_FLUSHING,
|
||||
STATE_DISCONNECTED
|
||||
} state{STATE_AWAITING_KEY};
|
||||
|
||||
bool finished_signal_send{false};
|
||||
|
||||
enum NetworkingProtocol {
|
||||
PROTOCOL_UNKNOWN,
|
||||
PROTOCOL_HTTPS,
|
||||
PROTOCOL_TS_V1
|
||||
};
|
||||
|
||||
enum HTTPUploadState {
|
||||
HTTP_AWAITING_HEADER,
|
||||
HTTP_STATE_AWAITING_BOUNDARY,
|
||||
HTTP_STATE_AWAITING_BOUNDARY_END,
|
||||
HTTP_STATE_UPLOADING,
|
||||
HTTP_STATE_DOWNLOADING
|
||||
};
|
||||
|
||||
struct {
|
||||
bool file_locked{false};
|
||||
int file_descriptor{0};
|
||||
|
||||
bool currently_processing{false};
|
||||
FileClient* next_client{nullptr};
|
||||
|
||||
bool query_media_bytes{false};
|
||||
uint8_t media_bytes[TRANSFER_MEDIA_BYTES_LENGTH]{0};
|
||||
uint8_t media_bytes_length{0};
|
||||
} file{};
|
||||
|
||||
struct {
|
||||
size_t provided_bytes{0};
|
||||
char key[TRANSFER_KEY_LENGTH]{0};
|
||||
} transfer_key{};
|
||||
|
||||
struct {
|
||||
std::mutex mutex{};
|
||||
size_t bytes{0};
|
||||
|
||||
bool buffering_stopped{false};
|
||||
bool write_disconnected{false};
|
||||
|
||||
Buffer* buffer_head{nullptr};
|
||||
Buffer** buffer_tail{&buffer_head};
|
||||
} network_buffer{};
|
||||
|
||||
struct {
|
||||
std::mutex mutex{};
|
||||
size_t bytes{0};
|
||||
|
||||
bool buffering_stopped{false};
|
||||
bool write_disconnected{false};
|
||||
|
||||
Buffer* buffer_head{nullptr};
|
||||
Buffer** buffer_tail{&buffer_head};
|
||||
} disk_buffer{};
|
||||
|
||||
struct {
|
||||
sockaddr_storage address{};
|
||||
int file_descriptor{0};
|
||||
|
||||
NetworkingProtocol protocol{PROTOCOL_UNKNOWN};
|
||||
|
||||
struct event* event_read{nullptr};
|
||||
struct event* event_write{nullptr};
|
||||
struct event* event_throttle{nullptr};
|
||||
|
||||
bool add_event_write{false}, add_event_read{false};
|
||||
|
||||
std::chrono::system_clock::time_point disconnect_timeout{};
|
||||
|
||||
networking::NetworkThrottle client_throttle{};
|
||||
/* the right side is the server throttle */
|
||||
networking::DualNetworkThrottle throttle{&client_throttle, &networking::NetworkThrottle::kNoThrottle};
|
||||
|
||||
pipes::SSL pipe_ssl{};
|
||||
bool pipe_ssl_init{false};
|
||||
std::unique_ptr<Buffer, decltype(free_buffer)*> http_header_buffer{nullptr, free_buffer};
|
||||
HTTPUploadState http_state{HTTPUploadState::HTTP_AWAITING_HEADER};
|
||||
|
||||
std::string http_boundary{};
|
||||
|
||||
/* Only read the transfer key length at the beginning. We than have the actual limit which will be set via throttle */
|
||||
size_t max_read_buffer_size{TRANSFER_KEY_LENGTH};
|
||||
} networking{};
|
||||
|
||||
struct {
|
||||
networking::TransferStatistics network_send{};
|
||||
networking::TransferStatistics network_received{};
|
||||
|
||||
networking::TransferStatistics file_transferred{};
|
||||
|
||||
networking::TransferStatistics disk_bytes_read{};
|
||||
networking::TransferStatistics disk_bytes_write{};
|
||||
} statistics{};
|
||||
|
||||
struct {
|
||||
std::chrono::system_clock::time_point last_write{};
|
||||
std::chrono::system_clock::time_point last_read{};
|
||||
|
||||
std::chrono::system_clock::time_point connected{};
|
||||
std::chrono::system_clock::time_point key_received{};
|
||||
std::chrono::system_clock::time_point disconnecting{};
|
||||
} timings;
|
||||
|
||||
explicit FileClient(LocalFileTransfer* handle) : handle{handle} { memtrack::allocated<FileClient>(this); }
|
||||
~FileClient();
|
||||
|
||||
void add_network_write_event(bool /* ignore bandwidth limits */);
|
||||
void add_network_write_event_nolock(bool /* ignore bandwidth limits */);
|
||||
|
||||
/* will check if we've enough space in out read buffer again */
|
||||
void add_network_read_event(bool /* ignore bandwidth limits */);
|
||||
|
||||
bool send_file_bytes(const void* /* buffer */, size_t /* length */);
|
||||
bool enqueue_network_buffer_bytes(const void* /* buffer */, size_t /* length */);
|
||||
bool enqueue_disk_buffer_bytes(const void* /* buffer */, size_t /* length */);
|
||||
|
||||
/* these function clear the buffers and set the write disconnected flags to true so no new buffers will be enqueued */
|
||||
size_t flush_network_buffer();
|
||||
void flush_disk_buffer();
|
||||
|
||||
[[nodiscard]] bool buffers_flushed();
|
||||
[[nodiscard]] inline std::string log_prefix() const { return "[" + net::to_string(this->networking.address) + "]"; }
|
||||
};
|
||||
|
||||
enum struct DiskIOStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct NetworkingStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct NetworkingBindResult {
|
||||
SUCCESS,
|
||||
|
||||
BINDING_ALREADY_EXISTS,
|
||||
NETWORKING_NOT_INITIALIZED,
|
||||
FAILED_TO_ALLOCATE_SOCKET, /* errno is set */
|
||||
FAILED_TO_BIND,
|
||||
FAILED_TO_LISTEN,
|
||||
|
||||
OUT_OF_MEMORY,
|
||||
};
|
||||
|
||||
enum struct NetworkingUnbindResult {
|
||||
SUCCESS,
|
||||
UNKNOWN_BINDING
|
||||
};
|
||||
|
||||
enum struct ClientWorkerStartResult {
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum struct NetworkInitializeResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct FileInitializeResult {
|
||||
SUCCESS,
|
||||
|
||||
INVALID_TRANSFER_DIRECTION,
|
||||
OUT_OF_MEMORY,
|
||||
|
||||
PROCESS_FILE_LIMIT_REACHED,
|
||||
SYSTEM_FILE_LIMIT_REACHED,
|
||||
|
||||
FILE_IS_BUSY,
|
||||
FILE_DOES_NOT_EXISTS,
|
||||
FILE_SYSTEM_ERROR,
|
||||
FILE_IS_A_DIRECTORY,
|
||||
|
||||
FILE_TOO_LARGE,
|
||||
DISK_IS_READ_ONLY,
|
||||
|
||||
FILE_SEEK_FAILED,
|
||||
FILE_SIZE_MISMATCH,
|
||||
|
||||
FILE_IS_NOT_ACCESSIBLE,
|
||||
|
||||
FAILED_TO_READ_MEDIA_BYTES,
|
||||
COUNT_NOT_CREATE_DIRECTORIES,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
constexpr static std::array<std::string_view, (size_t) FileInitializeResult::MAX> kFileInitializeResultMessages{
|
||||
/* SUCCESS */ "success",
|
||||
|
||||
/* INVALID_TRANSFER_DIRECTION */ "invalid file transfer direction",
|
||||
/* OUT_OF_MEMORY */ "out of memory",
|
||||
|
||||
/* PROCESS_FILE_LIMIT_REACHED */ "process file limit reached",
|
||||
/* SYSTEM_FILE_LIMIT_REACHED */ "system file limit reached",
|
||||
|
||||
/* FILE_IS_BUSY */ "target file is busy",
|
||||
/* FILE_DOES_NOT_EXISTS */ "target file does not exists",
|
||||
/* FILE_SYSTEM_ERROR */ "internal file system error",
|
||||
/* FILE_IS_A_DIRECTORY */ "target file is a directory",
|
||||
|
||||
/* FILE_TOO_LARGE */ "file is too large",
|
||||
/* DISK_IS_READ_ONLY */ "disk is in read only mode",
|
||||
|
||||
/* FILE_SEEK_FAILED */ "failed to seek to target file offset",
|
||||
/* FILE_SIZE_MISMATCH */ "file size miss match",
|
||||
/* FILE_IS_NOT_ACCESSIBLE */ "file is not accessible",
|
||||
/* FAILED_TO_READ_MEDIA_BYTES */ "failed to read file media bytes",
|
||||
/* COUNT_NOT_CREATE_DIRECTORIES */ "could not create required directories"
|
||||
};
|
||||
|
||||
enum struct TransferKeyApplyResult {
|
||||
SUCCESS,
|
||||
FILE_ERROR,
|
||||
UNKNOWN_KEY,
|
||||
|
||||
INTERNAL_ERROR
|
||||
};
|
||||
|
||||
enum struct TransferUploadRawResult {
|
||||
MORE_DATA_TO_RECEIVE,
|
||||
FINISH,
|
||||
FINISH_OVERFLOW,
|
||||
|
||||
/* UNKNOWN ERROR */
|
||||
};
|
||||
|
||||
enum struct TransferUploadHTTPResult {
|
||||
MORE_DATA_TO_RECEIVE,
|
||||
FINISH,
|
||||
|
||||
BOUNDARY_MISSING,
|
||||
BOUNDARY_TOKEN_INVALID,
|
||||
BOUNDARY_INVALID,
|
||||
|
||||
MISSING_CONTENT_TYPE,
|
||||
INVALID_CONTENT_TYPE
|
||||
/* UNKNOWN ERROR */
|
||||
};
|
||||
|
||||
struct NetworkBinding {
|
||||
std::string hostname{};
|
||||
sockaddr_storage address{};
|
||||
};
|
||||
|
||||
struct ActiveNetworkBinding : std::enable_shared_from_this<ActiveNetworkBinding> {
|
||||
std::string hostname{};
|
||||
sockaddr_storage address{};
|
||||
|
||||
int file_descriptor{-1};
|
||||
struct event* accept_event{nullptr};
|
||||
|
||||
LocalFileTransfer* handle{nullptr};
|
||||
};
|
||||
|
||||
class LocalFileTransfer : public AbstractProvider {
|
||||
public:
|
||||
explicit LocalFileTransfer(filesystem::LocalFileSystem*);
|
||||
~LocalFileTransfer();
|
||||
|
||||
[[nodiscard]] bool start();
|
||||
void stop();
|
||||
|
||||
[[nodiscard]] NetworkingBindResult add_network_binding(const NetworkBinding& /* binding */);
|
||||
[[nodiscard]] std::vector<NetworkBinding> active_network_bindings();
|
||||
[[nodiscard]] NetworkingUnbindResult remove_network_binding(const NetworkBinding& /* binding */);
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_channel_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, ChannelId channelId,
|
||||
const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> list_transfer() override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(const std::shared_ptr<VirtualFileServer>& /* server */, transfer_id id, bool) override;
|
||||
private:
|
||||
enum struct DiskIOLoopState {
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
|
||||
STOPPING,
|
||||
FORCE_STOPPING
|
||||
};
|
||||
filesystem::LocalFileSystem* file_system_;
|
||||
|
||||
size_t max_concurrent_transfers{1024};
|
||||
std::mt19937 transfer_random_token_generator{std::random_device{}()};
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::mutex transfers_mutex{};
|
||||
std::mutex transfer_create_mutex{};
|
||||
std::deque<std::shared_ptr<FileClient>> transfers_{};
|
||||
std::deque<std::shared_ptr<Transfer>> pending_transfers{};
|
||||
|
||||
enum ServerState {
|
||||
STOPPED,
|
||||
RUNNING
|
||||
} state{ServerState::STOPPED};
|
||||
|
||||
struct {
|
||||
bool active{false};
|
||||
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex mutex{};
|
||||
std::condition_variable notify_cv{};
|
||||
} disconnect;
|
||||
|
||||
struct {
|
||||
std::mutex mutex;
|
||||
|
||||
bool active{false};
|
||||
std::thread dispatch_thread{};
|
||||
struct event_base* event_base{nullptr};
|
||||
|
||||
std::deque<std::shared_ptr<ActiveNetworkBinding>> bindings{};
|
||||
} network{};
|
||||
|
||||
struct {
|
||||
DiskIOLoopState state{DiskIOLoopState::STOPPED};
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex queue_lock{};
|
||||
std::condition_variable notify_work_awaiting{};
|
||||
std::condition_variable notify_client_processed{};
|
||||
|
||||
FileClient* queue_head{nullptr};
|
||||
FileClient** queue_tail{&queue_head};
|
||||
} disk_io{};
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_transfer(Transfer::Direction, const std::shared_ptr<VirtualFileServer> &, ChannelId, Transfer::TargetType, const TransferInfo &info);
|
||||
|
||||
[[nodiscard]] NetworkingStartResult start_networking();
|
||||
[[nodiscard]] DiskIOStartResult start_disk_io();
|
||||
[[nodiscard]] ClientWorkerStartResult start_client_worker();
|
||||
|
||||
void shutdown_networking();
|
||||
void shutdown_disk_io();
|
||||
void shutdown_client_worker();
|
||||
|
||||
void disconnect_client(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */, bool /* flush network */);
|
||||
|
||||
[[nodiscard]] NetworkInitializeResult initialize_networking(const std::shared_ptr<FileClient>& /* client */, int /* file descriptor */);
|
||||
/* might block 'till all IO operations have been succeeded */
|
||||
void finalize_networking(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
[[nodiscard]] FileInitializeResult initialize_file_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void finalize_file_io(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
[[nodiscard]] bool initialize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
|
||||
void finalize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
void enqueue_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void execute_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
void test_disconnecting_state(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
[[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */, size_t* /* bytes written */);
|
||||
[[nodiscard]] TransferUploadHTTPResult handle_transfer_upload_http(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */);
|
||||
|
||||
void send_http_response(const std::shared_ptr<FileClient>& /* client */, http::HttpResponse& /* response */);
|
||||
|
||||
static void callback_transfer_network_write(int, short, void*);
|
||||
static void callback_transfer_network_read(int, short, void*);
|
||||
static void callback_transfer_network_throttle(int, short, void*);
|
||||
static void callback_transfer_network_accept(int, short, void*);
|
||||
|
||||
static void dispatch_loop_client_worker(void*);
|
||||
static void dispatch_loop_network(void*);
|
||||
static void dispatch_loop_disk_io(void*);
|
||||
|
||||
void report_transfer_statistics(const std::shared_ptr<FileClient>& /* client */);
|
||||
[[nodiscard]] TransferStatistics generate_transfer_statistics_report(const std::shared_ptr<FileClient>& /* client */);
|
||||
void invoke_aborted_callback(const std::shared_ptr<FileClient>& /* client */, const TransferError& /* error */);
|
||||
void invoke_aborted_callback(const std::shared_ptr<Transfer>& /* pending transfer */, const TransferError& /* error */);
|
||||
|
||||
size_t handle_transfer_read(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
|
||||
size_t handle_transfer_read_raw(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
|
||||
[[nodiscard]] TransferKeyApplyResult handle_transfer_key_provided(const std::shared_ptr<FileClient>& /* client */, std::string& /* error */);
|
||||
};
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <event2/event.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "LocalFileProvider.h"
|
||||
#include "./LocalFileTransfer.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
@@ -37,40 +37,24 @@ void LocalFileTransfer::shutdown_client_worker() {
|
||||
void LocalFileTransfer::disconnect_client(const std::shared_ptr<FileClient> &client, std::unique_lock<std::shared_mutex>& state_lock, bool flush) {
|
||||
assert(state_lock.owns_lock());
|
||||
|
||||
if(client->state == FileClient::STATE_DISCONNECTED || (client->state == FileClient::STATE_DISCONNECTING && flush)) {
|
||||
if(client->state == FileClient::STATE_DISCONNECTED || (client->state == FileClient::STATE_FLUSHING && flush)) {
|
||||
return; /* shall NOT happen */
|
||||
}
|
||||
|
||||
#define del_ev_noblock(event) if(event) event_del_noblock(event)
|
||||
|
||||
client->state = flush ? FileClient::STATE_DISCONNECTING : FileClient::STATE_DISCONNECTED;
|
||||
client->state = flush ? FileClient::STATE_FLUSHING : FileClient::STATE_DISCONNECTED;
|
||||
client->timings.disconnecting = std::chrono::system_clock::now();
|
||||
if(flush) {
|
||||
const auto network_flush_time = client->networking.throttle.expected_writing_time(client->buffer.bytes) + std::chrono::seconds{10};
|
||||
const auto network_flush_time = client->networking.throttle.expected_writing_time(client->network_buffer.bytes) + std::chrono::seconds{10};
|
||||
|
||||
if(!client->transfer) {
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
del_ev_noblock(client->networking.event_throttle);
|
||||
client->add_network_write_event_nolock(false);
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
|
||||
/* max flush 10 seconds */
|
||||
client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time;
|
||||
debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor<std::chrono::seconds>(network_flush_time).count());
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
del_ev_noblock(client->networking.event_write);
|
||||
del_ev_noblock(client->networking.event_throttle);
|
||||
client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time;
|
||||
debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor<std::chrono::seconds>(network_flush_time).count());
|
||||
|
||||
/* no direct timeout needed here, we're just flushing the disk */
|
||||
this->enqueue_disk_io(client);
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
client->add_network_write_event_nolock(false);
|
||||
|
||||
/* max flush 10 seconds */
|
||||
client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time;
|
||||
debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor<std::chrono::seconds>(network_flush_time).count());
|
||||
}
|
||||
client->add_network_write_event_nolock(false);
|
||||
this->enqueue_disk_io(client);
|
||||
} else {
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
del_ev_noblock(client->networking.event_write);
|
||||
@@ -82,13 +66,25 @@ void LocalFileTransfer::disconnect_client(const std::shared_ptr<FileClient> &cli
|
||||
#undef del_ev_noblock
|
||||
}
|
||||
|
||||
void LocalFileTransfer::test_disconnecting_state(const std::shared_ptr<FileClient> &client) {
|
||||
if(client->state != FileClient::STATE_FLUSHING)
|
||||
return;
|
||||
|
||||
if(!client->buffers_flushed())
|
||||
return;
|
||||
|
||||
debugMessage(LOG_FT, "{} Disk and network buffers are flushed. Closing connection.", client->log_prefix());
|
||||
std::unique_lock s_lock{client->state_mutex};
|
||||
this->disconnect_client(client, s_lock, false);
|
||||
}
|
||||
|
||||
void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
|
||||
auto provider = reinterpret_cast<LocalFileTransfer*>(ptr_transfer);
|
||||
|
||||
while(provider->disconnect.active) {
|
||||
{
|
||||
std::unique_lock dlock{provider->disconnect.mutex};
|
||||
provider->disconnect.notify_cv.wait_for(dlock, std::chrono::seconds{1});
|
||||
provider->disconnect.notify_cv.wait_for(dlock, std::chrono::milliseconds {500}); /* report all 500ms the statistics */
|
||||
}
|
||||
/* run the disconnect worker at least once before exiting */
|
||||
|
||||
@@ -101,14 +97,24 @@ void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
|
||||
switch(transfer->state) {
|
||||
case FileClient::STATE_TRANSFERRING:
|
||||
break;
|
||||
case FileClient::STATE_DISCONNECTING:
|
||||
if(transfer->transfer && transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD)
|
||||
break; /* we're still transferring (sending data) */
|
||||
case FileClient::STATE_FLUSHING:
|
||||
if(!transfer->transfer)
|
||||
continue;
|
||||
|
||||
if(transfer->transfer->direction != Transfer::DIRECTION_DOWNLOAD)
|
||||
continue;
|
||||
|
||||
if(transfer->buffers_flushed())
|
||||
continue;
|
||||
|
||||
break; /* we're still transferring (sending data) */
|
||||
case FileClient::STATE_AWAITING_KEY:
|
||||
case FileClient::STATE_DISCONNECTED:
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
provider->report_transfer_statistics(transfer->shared_from_this());
|
||||
provider->report_transfer_statistics(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,17 +126,15 @@ void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::copy_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), std::back_inserter(timeouted_transfers), [&](const std::shared_ptr<Transfer>& t) {
|
||||
return t->initialized_timestamp + std::chrono::seconds{100} < now; //FIXME: Decrease to 10 again!
|
||||
return t->initialized_timestamp + std::chrono::seconds{10} < now;
|
||||
});
|
||||
provider->pending_transfers.erase(std::remove_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), [&](const auto& t) {
|
||||
return std::find(timeouted_transfers.begin(), timeouted_transfers.end(), t) != timeouted_transfers.end();
|
||||
}), provider->pending_transfers.end());
|
||||
}
|
||||
|
||||
for(const auto& pt : timeouted_transfers) {
|
||||
if(auto callback{provider->callback_transfer_aborted}; callback)
|
||||
callback(pt, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
}
|
||||
for(const auto& pt : timeouted_transfers)
|
||||
provider->invoke_aborted_callback(pt, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
|
||||
if(!timeouted_transfers.empty())
|
||||
logMessage(LOG_FT, "Removed {} pending transfers because no request has been made for them.", timeouted_transfers.size());
|
||||
@@ -152,12 +156,11 @@ void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
|
||||
} else if(t->state == FileClient::STATE_TRANSFERRING) {
|
||||
assert(t->transfer);
|
||||
if(t->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
return false; //FIXME: Due to debugging reasons
|
||||
return t->timings.last_read + std::chrono::seconds{5} < now;
|
||||
} else if(t->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
return t->timings.last_write + std::chrono::seconds{5} < now;
|
||||
}
|
||||
} else if(t->state == FileClient::STATE_DISCONNECTING) {
|
||||
} else if(t->state == FileClient::STATE_FLUSHING) {
|
||||
if(t->networking.disconnect_timeout.time_since_epoch().count() > 0)
|
||||
return t->networking.disconnect_timeout + std::chrono::seconds{5} < now;
|
||||
return t->timings.disconnecting + std::chrono::seconds{30} < now;
|
||||
@@ -176,21 +179,29 @@ void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
|
||||
break;
|
||||
case FileClient::STATE_TRANSFERRING:
|
||||
logMessage(LOG_FT, "{} Networking timeout. Dropping client", client->log_prefix());
|
||||
if(auto callback{provider->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
provider->invoke_aborted_callback(client, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
break;
|
||||
case FileClient::STATE_DISCONNECTING:
|
||||
logMessage(LOG_FT, "{} Failed to flush connection. Dropping client", client->log_prefix());
|
||||
case FileClient::STATE_FLUSHING:
|
||||
if(!client->buffers_flushed())
|
||||
logMessage(LOG_FT, "{} Failed to flush connection. Dropping client", client->log_prefix());
|
||||
else
|
||||
; /* we just awaited a client disconnect */
|
||||
break;
|
||||
case FileClient::STATE_DISCONNECTED:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->state = FileClient::STATE_DISCONNECTED;
|
||||
provider->finalize_file_io(client, slock);
|
||||
provider->finalize_client_ssl(client);
|
||||
/*
|
||||
* First of all disconnect the client from the network so no actions could be triggered by that way.
|
||||
* Secondly finalize all network components, so no data is pending anywhere
|
||||
* Thirdly drop the client's disk worker (if it's an upload the data should be written already, else we don't care anyways)
|
||||
*/
|
||||
provider->finalize_networking(client, slock);
|
||||
provider->finalize_client_ssl(client);
|
||||
provider->finalize_file_io(client, slock);
|
||||
}
|
||||
|
||||
debugMessage(LOG_FT, "{} Destroying transfer.", client->log_prefix());
|
||||
@@ -203,20 +214,58 @@ void LocalFileTransfer::report_transfer_statistics(const std::shared_ptr<FileCli
|
||||
auto callback{this->callback_transfer_statistics};
|
||||
if(!callback) return;
|
||||
|
||||
callback(client->transfer, this->generate_transfer_statistics_report(client));
|
||||
}
|
||||
|
||||
TransferStatistics LocalFileTransfer::generate_transfer_statistics_report(const std::shared_ptr<FileClient> &client) {
|
||||
TransferStatistics stats{};
|
||||
|
||||
stats.network_bytes_send = client->statistics.network_bytes_send;
|
||||
stats.network_bytes_received = client->statistics.network_bytes_received;
|
||||
stats.file_bytes_transferred = client->statistics.file_bytes_transferred;
|
||||
stats.network_bytes_send = client->statistics.network_send.total_bytes;
|
||||
stats.network_bytes_received = client->statistics.network_received.total_bytes;
|
||||
stats.file_bytes_transferred = client->statistics.file_transferred.total_bytes;
|
||||
|
||||
stats.delta_network_bytes_received = stats.network_bytes_received - std::exchange(client->statistics.last_network_bytes_received, stats.network_bytes_received);
|
||||
stats.delta_network_bytes_send = stats.network_bytes_send - std::exchange(client->statistics.last_network_bytes_send, stats.network_bytes_send);
|
||||
stats.delta_network_bytes_received = client->statistics.network_received.take_delta();
|
||||
stats.delta_network_bytes_send = client->statistics.network_received.take_delta();
|
||||
|
||||
stats.delta_file_bytes_transferred = stats.file_bytes_transferred - std::exchange(client->statistics.last_file_bytes_transferred, stats.file_bytes_transferred);
|
||||
stats.delta_file_bytes_transferred = client->statistics.file_transferred.take_delta();
|
||||
|
||||
stats.file_start_offset = client->transfer->file_offset;
|
||||
stats.file_current_offset = client->statistics.file_bytes_transferred + client->transfer->file_offset;
|
||||
stats.file_current_offset = client->statistics.file_transferred.total_bytes + client->transfer->file_offset;
|
||||
stats.file_total_size = client->transfer->expected_file_size;
|
||||
|
||||
callback(client->transfer, stats);
|
||||
stats.average_speed = client->statistics.file_transferred.average_bandwidth();
|
||||
stats.current_speed = client->statistics.file_transferred.current_bandwidth();
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::invoke_aborted_callback(const std::shared_ptr<FileClient> &client,
|
||||
const ts::server::file::transfer::TransferError &error) {
|
||||
auto callback{this->callback_transfer_aborted};
|
||||
if(!callback || !client->transfer) return;
|
||||
|
||||
callback(client->transfer, this->generate_transfer_statistics_report(client), error);
|
||||
}
|
||||
|
||||
void LocalFileTransfer::invoke_aborted_callback(const std::shared_ptr<Transfer> &transfer,
|
||||
const ts::server::file::transfer::TransferError &error) {
|
||||
auto callback{this->callback_transfer_aborted};
|
||||
if(!callback) return;
|
||||
|
||||
TransferStatistics stats{};
|
||||
|
||||
stats.network_bytes_send = 0;
|
||||
stats.network_bytes_received = 0;
|
||||
stats.file_bytes_transferred = 0;
|
||||
|
||||
stats.delta_network_bytes_received = 0;
|
||||
stats.delta_network_bytes_send = 0;
|
||||
|
||||
stats.delta_file_bytes_transferred = 0;
|
||||
|
||||
stats.file_start_offset = transfer->file_offset;
|
||||
stats.file_current_offset = transfer->file_offset;
|
||||
stats.file_total_size = transfer->expected_file_size;
|
||||
|
||||
callback(transfer, stats, error);
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
#include <experimental/filesystem>
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "./duration_utils.h"
|
||||
#include "LocalFileProvider.h"
|
||||
#include "./LocalFileTransfer.h"
|
||||
#include "duration_utils.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
@@ -62,7 +62,13 @@ void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
|
||||
provider->disk_io.notify_work_awaiting.wait(qlock, [&]{ return provider->disk_io.state != DiskIOLoopState::RUNNING || provider->disk_io.queue_head != nullptr; });
|
||||
|
||||
if(provider->disk_io.queue_head) {
|
||||
client = provider->disk_io.queue_head->shared_from_this();
|
||||
try {
|
||||
client = provider->disk_io.queue_head->shared_from_this();
|
||||
} catch (std::bad_weak_ptr& ex) {
|
||||
logCritical(LOG_FT, "Disk worker encountered a bad weak ptr. This indicated something went horribly wrong! Please submit this on https://forum.teaspeak.de !!!");
|
||||
client.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
provider->disk_io.queue_head = provider->disk_io.queue_head->file.next_client;
|
||||
if(!provider->disk_io.queue_head)
|
||||
@@ -98,6 +104,64 @@ void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
|
||||
provider->disk_io.notify_client_processed.notify_all();
|
||||
}
|
||||
|
||||
bool FileClient::enqueue_disk_buffer_bytes(const void *snd_buffer, size_t size) {
|
||||
auto tbuffer = allocate_buffer(size);
|
||||
tbuffer->length = size;
|
||||
tbuffer->offset = 0;
|
||||
memcpy(tbuffer->data, snd_buffer, size);
|
||||
|
||||
size_t buffer_size;
|
||||
{
|
||||
std::lock_guard block{this->disk_buffer.mutex};
|
||||
if(this->disk_buffer.write_disconnected)
|
||||
goto write_disconnected;
|
||||
|
||||
*this->disk_buffer.buffer_tail = tbuffer;
|
||||
this->disk_buffer.buffer_tail = &tbuffer->next;
|
||||
buffer_size = (this->disk_buffer.bytes += size);
|
||||
}
|
||||
|
||||
return buffer_size > TRANSFER_MAX_CACHED_BYTES;
|
||||
|
||||
write_disconnected:
|
||||
free_buffer(tbuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileClient::flush_disk_buffer() {
|
||||
Buffer* current_head;
|
||||
{
|
||||
std::lock_guard block{this->disk_buffer.mutex};
|
||||
|
||||
this->disk_buffer.write_disconnected = true;
|
||||
this->disk_buffer.bytes = 0;
|
||||
current_head = std::exchange(this->disk_buffer.buffer_head, nullptr);
|
||||
this->disk_buffer.buffer_tail = &this->disk_buffer.buffer_head;
|
||||
}
|
||||
|
||||
while(current_head) {
|
||||
auto next = current_head->next;
|
||||
free_buffer(current_head);
|
||||
current_head = next;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileClient::buffers_flushed() {
|
||||
{
|
||||
std::lock_guard db_lock{this->disk_buffer.mutex};
|
||||
if(this->disk_buffer.bytes > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard nb_lock{this->network_buffer.mutex};
|
||||
if(this->network_buffer.bytes > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr<FileClient> &transfer) {
|
||||
FileInitializeResult result{FileInitializeResult::SUCCESS};
|
||||
assert(transfer->transfer);
|
||||
@@ -107,21 +171,34 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
assert(!file_data.file_descriptor);
|
||||
assert(!file_data.next_client);
|
||||
|
||||
auto absolute_path = transfer->transfer->absolute_file_path;
|
||||
{
|
||||
unsigned int open_flags{0};
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
open_flags = O_RDONLY;
|
||||
|
||||
std::error_code fs_error{};
|
||||
if(file_data.absolute_path.empty() || !fs::exists(file_data.absolute_path, fs_error)) {
|
||||
if(absolute_path.empty() || !fs::exists(absolute_path, fs_error)) {
|
||||
result = FileInitializeResult::FILE_DOES_NOT_EXISTS;
|
||||
goto error_exit;
|
||||
} else if(fs_error) {
|
||||
logWarning(LOG_FT, "{} Failed to check for file existence of {}: {}/{}", transfer->log_prefix(), file_data.absolute_path, fs_error.value(), fs_error.message());
|
||||
logWarning(LOG_FT, "{} Failed to check for file existence of {}: {}/{}", transfer->log_prefix(), absolute_path, fs_error.value(), fs_error.message());
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
goto error_exit;
|
||||
}
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
std::error_code fs_error{};
|
||||
auto parent_path = fs::u8path(absolute_path).parent_path();
|
||||
if(!fs::exists(parent_path)) {
|
||||
if(!fs::create_directories(parent_path, fs_error) || fs_error) {
|
||||
logError(LOG_FT, "{} Failed to create required directories for file upload for {}: {}/{}", transfer->log_prefix(), absolute_path, fs_error.value(), fs_error.message());
|
||||
result = FileInitializeResult::COUNT_NOT_CREATE_DIRECTORIES;
|
||||
goto error_exit;
|
||||
}
|
||||
} else if(fs_error) {
|
||||
logWarning(LOG_FT, "{} Failed to check for directory existence of {}: {}/{}. Assuming it exists", transfer->log_prefix(), parent_path.string(), fs_error.value(), fs_error.message());
|
||||
}
|
||||
|
||||
open_flags = (unsigned) O_WRONLY | (unsigned) O_CREAT;
|
||||
if(transfer->transfer->override_exiting)
|
||||
open_flags |= (unsigned) O_TRUNC;
|
||||
@@ -129,7 +206,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
return FileInitializeResult::INVALID_TRANSFER_DIRECTION;
|
||||
}
|
||||
|
||||
file_data.file_descriptor = open(file_data.absolute_path.c_str(), (int) open_flags, 0644);
|
||||
file_data.file_descriptor = open(absolute_path.c_str(), (int) open_flags, 0644);
|
||||
if(file_data.file_descriptor <= 0) {
|
||||
const auto errno_ = errno;
|
||||
switch (errno_) {
|
||||
@@ -137,7 +214,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
break;
|
||||
case EDQUOT:
|
||||
logWarning(LOG_FT, "{} Disk inode limit has been reached. Failed to start file transfer for file {}", transfer->log_prefix(), file_data.absolute_path);
|
||||
logWarning(LOG_FT, "{} Disk inode limit has been reached. Failed to start file transfer for file {}", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
break;
|
||||
case EISDIR:
|
||||
@@ -156,7 +233,8 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
result = FileInitializeResult::DISK_IS_READ_ONLY;
|
||||
break;
|
||||
default:
|
||||
logWarning(LOG_FT, "{} Failed to start file transfer for file {}: {}/{}", transfer->log_prefix(), file_data.absolute_path, errno_, strerror(errno_));
|
||||
logWarning(LOG_FT, "{} Failed to start file {} for file {}: {}/{}", transfer->log_prefix(),
|
||||
transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD ? "download" : "upload", absolute_path, errno_, strerror(errno_));
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
break;
|
||||
}
|
||||
@@ -164,7 +242,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
}
|
||||
}
|
||||
|
||||
this->file_system_.lock_file(transfer->file.absolute_path);
|
||||
this->file_system_->lock_file(absolute_path);
|
||||
transfer->file.file_locked = true;
|
||||
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
@@ -172,7 +250,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
const auto errno_ = errno;
|
||||
switch (errno_) {
|
||||
case EACCES:
|
||||
logWarning(LOG_FT, "{} File {} got inaccessible on truncating, but not on opening.", transfer->log_prefix(), file_data.absolute_path);
|
||||
logWarning(LOG_FT, "{} File {} got inaccessible on truncating, but not on opening.", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
|
||||
@@ -181,16 +259,16 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
goto error_exit;
|
||||
|
||||
case EIO:
|
||||
logWarning(LOG_FT, "{} A disk IO error occurred while resizing file {}.", transfer->log_prefix(), file_data.absolute_path);
|
||||
logWarning(LOG_FT, "{} A disk IO error occurred while resizing file {}.", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
|
||||
case EROFS:
|
||||
logWarning(LOG_FT, "{} Failed to resize file {} because disk is in read only mode.", transfer->log_prefix(), file_data.absolute_path);
|
||||
logWarning(LOG_FT, "{} Failed to resize file {} because disk is in read only mode.", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
default:
|
||||
debugMessage(LOG_FT, "{} Failed to truncate file {}: {}/{}. Trying to upload file anyways.", transfer->log_prefix(), file_data.absolute_path, errno_, strerror(errno_));
|
||||
debugMessage(LOG_FT, "{} Failed to truncate file {}: {}/{}. Trying to upload file anyways.", transfer->log_prefix(), absolute_path, errno_, strerror(errno_));
|
||||
}
|
||||
}
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
@@ -200,7 +278,24 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
result = FileInitializeResult::FILE_SIZE_MISMATCH;
|
||||
goto error_exit;
|
||||
}
|
||||
if(transfer->file.query_media_bytes && file_size > 0) {
|
||||
auto new_pos = lseek(file_data.file_descriptor, 0, SEEK_SET);
|
||||
if(new_pos < 0) {
|
||||
logWarning(LOG_FT, "{} Failed to seek to file start: {}/{}", transfer->log_prefix(), errno, strerror(errno));
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
auto read = ::read(file_data.file_descriptor, transfer->file.media_bytes, TRANSFER_MEDIA_BYTES_LENGTH);
|
||||
if(read <= 0) {
|
||||
logWarning(LOG_FT, "{} Failed to read file media bytes: {}/{}", transfer->log_prefix(), errno, strerror(errno));
|
||||
result = FileInitializeResult::FAILED_TO_READ_MEDIA_BYTES;
|
||||
goto error_exit;
|
||||
}
|
||||
transfer->file.media_bytes_length = read;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto new_pos = lseek(file_data.file_descriptor, transfer->transfer->file_offset, SEEK_SET);
|
||||
if(new_pos < 0) {
|
||||
@@ -212,13 +307,13 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
}
|
||||
debugMessage(LOG_FT, "{} Seek to file offset {}. New actual offset is {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
|
||||
logTrace(LOG_FT, "{} Seek to file offset {}. New actual offset is {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
|
||||
}
|
||||
|
||||
return FileInitializeResult::SUCCESS;
|
||||
error_exit:
|
||||
if(std::exchange(transfer->file.file_locked, false))
|
||||
this->file_system_.unlock_file(transfer->file.absolute_path);
|
||||
this->file_system_->unlock_file(absolute_path);
|
||||
|
||||
if(file_data.file_descriptor > 0)
|
||||
::close(file_data.file_descriptor);
|
||||
@@ -230,6 +325,11 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
|
||||
void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &transfer,
|
||||
std::unique_lock<std::shared_mutex> &state_lock) {
|
||||
assert(state_lock.owns_lock());
|
||||
if(!transfer->transfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto absolute_path = transfer->transfer->absolute_file_path;
|
||||
|
||||
auto& file_data = transfer->file;
|
||||
|
||||
@@ -242,33 +342,31 @@ void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &tran
|
||||
continue;
|
||||
}
|
||||
|
||||
if(file_data.next_client) {
|
||||
if(this->disk_io.queue_head == &*transfer) {
|
||||
this->disk_io.queue_head = file_data.next_client;
|
||||
if(!this->disk_io.queue_head)
|
||||
this->disk_io.queue_tail = &this->disk_io.queue_head;
|
||||
} else {
|
||||
FileClient* head{this->disk_io.queue_head};
|
||||
while(head->file.next_client != &*transfer) {
|
||||
assert(head->file.next_client);
|
||||
head = head->file.next_client;
|
||||
}
|
||||
|
||||
head->file.next_client = file_data.next_client;
|
||||
if(!file_data.next_client)
|
||||
this->disk_io.queue_tail = &head->file.next_client;
|
||||
|
||||
if(this->disk_io.queue_head == &*transfer) {
|
||||
this->disk_io.queue_head = file_data.next_client;
|
||||
if (!this->disk_io.queue_head) {
|
||||
this->disk_io.queue_tail = &this->disk_io.queue_head;
|
||||
}
|
||||
} else if(file_data.next_client || this->disk_io.queue_tail == &file_data.next_client) {
|
||||
FileClient* head{this->disk_io.queue_head};
|
||||
while(head->file.next_client != &*transfer) {
|
||||
assert(head->file.next_client);
|
||||
head = head->file.next_client;
|
||||
}
|
||||
file_data.next_client = nullptr;
|
||||
}
|
||||
|
||||
head->file.next_client = file_data.next_client;
|
||||
if(!file_data.next_client)
|
||||
this->disk_io.queue_tail = &head->file.next_client;
|
||||
}
|
||||
file_data.next_client = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
state_lock.lock();
|
||||
|
||||
if(std::exchange(file_data.file_locked, false))
|
||||
this->file_system_.unlock_file(file_data.absolute_path);
|
||||
if(std::exchange(file_data.file_locked, false)) {
|
||||
this->file_system_->unlock_file(absolute_path);
|
||||
}
|
||||
|
||||
if(file_data.file_descriptor > 0)
|
||||
::close(file_data.file_descriptor);
|
||||
@@ -276,18 +374,22 @@ void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &tran
|
||||
}
|
||||
|
||||
void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr<FileClient> &client) {
|
||||
if(!client->file.file_descriptor)
|
||||
if(!client->file.file_descriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!client->transfer)
|
||||
if(!client->transfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
if(client->state != FileClient::STATE_TRANSFERRING)
|
||||
if(client->state != FileClient::STATE_TRANSFERRING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(client->buffer.bytes > TRANSFER_MAX_CACHED_BYTES)
|
||||
if(client->disk_buffer.bytes > TRANSFER_MAX_CACHED_BYTES) {
|
||||
return;
|
||||
}
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
/* we don't do this check because this might be a flush instruction, where the buffer is actually zero bytes filled */
|
||||
/*
|
||||
@@ -297,8 +399,9 @@ void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr<FileClient> &clien
|
||||
}
|
||||
|
||||
std::lock_guard dlock{this->disk_io.queue_lock};
|
||||
if(client->file.next_client || this->disk_io.queue_tail == &client->file.next_client)
|
||||
if(client->file.next_client || this->disk_io.queue_tail == &client->file.next_client) {
|
||||
return;
|
||||
}
|
||||
|
||||
*this->disk_io.queue_tail = &*client;
|
||||
this->disk_io.queue_tail = &client->file.next_client;
|
||||
@@ -310,14 +413,14 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
|
||||
if(!client->transfer) return;
|
||||
|
||||
if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
Buffer* buffer{nullptr};
|
||||
size_t buffer_left_size{0};
|
||||
Buffer* buffer;
|
||||
size_t buffer_left_size;
|
||||
|
||||
while(true) {
|
||||
{
|
||||
std::lock_guard block{client->buffer.mutex};
|
||||
buffer = client->buffer.buffer_head;
|
||||
buffer_left_size = client->buffer.bytes;
|
||||
std::lock_guard block{client->disk_buffer.mutex};
|
||||
buffer = client->disk_buffer.buffer_head;
|
||||
buffer_left_size = client->disk_buffer.bytes;
|
||||
}
|
||||
if(!buffer) {
|
||||
assert(buffer_left_size == 0);
|
||||
@@ -329,15 +432,15 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
|
||||
if(written <= 0) {
|
||||
if(written == 0) {
|
||||
/* EOF, how the hell is this event possible?! */
|
||||
auto offset_written = client->statistics.disk_bytes_write + client->transfer->file_offset;
|
||||
auto offset_written = client->statistics.disk_bytes_write.total_bytes + client->transfer->file_offset;
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received unexpected file write EOF. EOF received at {} but expected {}. Actual file offset: {}. Closing transfer.",
|
||||
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{client->handle->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
|
||||
|
||||
this->invoke_aborted_callback(client, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
|
||||
|
||||
client->flush_disk_buffer();
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
@@ -349,15 +452,14 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
|
||||
break;
|
||||
}
|
||||
|
||||
auto offset_written = client->statistics.disk_bytes_write + client->transfer->file_offset;
|
||||
auto offset_written = client->statistics.disk_bytes_write.total_bytes + client->transfer->file_offset;
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received write to disk IO error. Write pointer is at {} of {}. Actual file offset: {}. Closing transfer.",
|
||||
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{client->handle->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
this->invoke_aborted_callback(client, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
|
||||
client->flush_disk_buffer();
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
@@ -369,46 +471,46 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
|
||||
assert(buffer->offset <= buffer->length);
|
||||
if(buffer->length == buffer->offset) {
|
||||
{
|
||||
std::lock_guard block{client->buffer.mutex};
|
||||
client->buffer.buffer_head = buffer->next;
|
||||
std::lock_guard block{client->disk_buffer.mutex};
|
||||
client->disk_buffer.buffer_head = buffer->next;
|
||||
if(!buffer->next)
|
||||
client->buffer.buffer_tail = &client->buffer.buffer_head;
|
||||
client->disk_buffer.buffer_tail = &client->disk_buffer.buffer_head;
|
||||
|
||||
assert(client->buffer.bytes >= written);
|
||||
client->buffer.bytes -= written;
|
||||
buffer_left_size = client->buffer.bytes;
|
||||
assert(client->disk_buffer.bytes >= written);
|
||||
client->disk_buffer.bytes -= written;
|
||||
buffer_left_size = client->disk_buffer.bytes;
|
||||
(void) buffer_left_size; /* trick my IDE here a bit */
|
||||
}
|
||||
|
||||
free_buffer(buffer);
|
||||
} else {
|
||||
std::lock_guard block{client->buffer.mutex};
|
||||
assert(client->buffer.bytes >= written);
|
||||
client->buffer.bytes -= written;
|
||||
buffer_left_size = client->buffer.bytes;
|
||||
std::lock_guard block{client->disk_buffer.mutex};
|
||||
assert(client->disk_buffer.bytes >= written);
|
||||
client->disk_buffer.bytes -= written;
|
||||
buffer_left_size = client->disk_buffer.bytes;
|
||||
(void) buffer_left_size; /* trick my IDE here a bit */
|
||||
}
|
||||
|
||||
client->statistics.disk_bytes_write += written;
|
||||
client->statistics.disk_bytes_write.increase_bytes(written);
|
||||
}
|
||||
}
|
||||
|
||||
if(buffer_left_size > 0)
|
||||
if(buffer_left_size > 0) {
|
||||
this->enqueue_disk_io(client);
|
||||
else if(client->state == FileClient::STATE_DISCONNECTING) {
|
||||
debugMessage(LOG_FT, "{} Disk IO has been flushed.", client->log_prefix());
|
||||
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client->shared_from_this(), slock, false);
|
||||
} else if(client->state == FileClient::STATE_FLUSHING) {
|
||||
this->test_disconnecting_state(client);
|
||||
}
|
||||
|
||||
if(client->state == FileClient::STATE_TRANSFERRING && buffer_left_size < TRANSFER_MAX_CACHED_BYTES / 2) {
|
||||
if(client->buffer.buffering_stopped)
|
||||
if(client->disk_buffer.buffering_stopped)
|
||||
logMessage(LOG_FT, "{} Starting network read, buffer is capable for reading again.", client->log_prefix());
|
||||
client->add_network_read_event(false);
|
||||
}
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
if(client->state == FileClient::STATE_DISCONNECTING) return;
|
||||
if(client->state == FileClient::STATE_FLUSHING) {
|
||||
client->flush_disk_buffer(); /* just in case, file download usually don't write to the disk */
|
||||
return;
|
||||
}
|
||||
|
||||
while(true) {
|
||||
constexpr auto buffer_capacity{4096};
|
||||
@@ -418,7 +520,7 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
|
||||
if(read <= 0) {
|
||||
if(read == 0) {
|
||||
/* EOF */
|
||||
auto offset_send = client->statistics.disk_bytes_read + client->transfer->file_offset;
|
||||
auto offset_send = client->statistics.disk_bytes_read.total_bytes + client->transfer->file_offset;
|
||||
if(client->transfer->expected_file_size == offset_send) {
|
||||
debugMessage(LOG_FT, "{} Finished file reading. Flushing and disconnecting transfer. Reading took {} seconds.",
|
||||
client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received));
|
||||
@@ -427,14 +529,7 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
|
||||
logError(LOG_FT, "{} Received unexpected read EOF. EOF received at {} but expected {}. Actual file offset: {}. Disconnecting client.",
|
||||
client->log_prefix(), offset_send, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{client->handle->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
this->invoke_aborted_callback(client, { TransferError::UNEXPECTED_DISK_EOF, "" });
|
||||
}
|
||||
} else {
|
||||
if(errno == EAGAIN) {
|
||||
@@ -442,26 +537,22 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
|
||||
return;
|
||||
}
|
||||
|
||||
logWarning(LOG_FT, "{} Failed to read from file {} ({}/{}). Aborting transfer.", client->log_prefix(), client->file.absolute_path, errno, strerror(errno));
|
||||
logWarning(LOG_FT, "{} Failed to read from file {} ({}/{}). Aborting transfer.", client->log_prefix(), client->transfer->absolute_file_path, errno, strerror(errno));
|
||||
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{client->handle->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
}
|
||||
this->invoke_aborted_callback(client, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
}
|
||||
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
return;
|
||||
} else {
|
||||
auto buffer_full = client->send_file_bytes(buffer, read);
|
||||
client->statistics.disk_bytes_read += read;
|
||||
client->statistics.file_bytes_transferred += read;
|
||||
client->statistics.disk_bytes_read.increase_bytes(read);
|
||||
client->statistics.file_transferred.increase_bytes(read);
|
||||
|
||||
std::shared_lock slock{client->state_mutex};
|
||||
if(buffer_full) {
|
||||
logMessage(LOG_FT, "{} Stopping buffering from disk. Buffer full ({}bytes)", client->log_prefix(), client->buffer.bytes);
|
||||
//logTrace(LOG_FT, "{} Stopping buffering from disk. Buffer full ({}bytes)", client->log_prefix(), client->buffer.bytes);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by WolverinDEV on 12/05/2020.
|
||||
//
|
||||
|
||||
#include "./NetTools.h"
|
||||
|
||||
using namespace ts::server::file::networking;
|
||||
|
||||
NetworkThrottle NetworkThrottle::kNoThrottle{-1};
|
||||
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <numeric>
|
||||
|
||||
namespace ts::server::file::networking {
|
||||
struct NetworkThrottle {
|
||||
constexpr static auto kThrottleTimespanMs{250};
|
||||
|
||||
typedef uint8_t span_t;
|
||||
|
||||
static NetworkThrottle kNoThrottle;
|
||||
|
||||
ssize_t max_bytes{0};
|
||||
|
||||
span_t current_index{0};
|
||||
size_t bytes_send{0};
|
||||
|
||||
mutable spin_mutex mutex{};
|
||||
|
||||
inline bool increase_bytes(size_t bytes) {
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(this->current_index != current_span) {
|
||||
this->current_index = current_span;
|
||||
this->bytes_send = bytes;
|
||||
} else {
|
||||
this->bytes_send += bytes;
|
||||
}
|
||||
return this->max_bytes > 0 && this->bytes_send >= this->max_bytes;
|
||||
}
|
||||
|
||||
inline void set_max_bandwidth(ssize_t bytes_per_second) {
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(bytes_per_second <= 0)
|
||||
this->max_bytes = -1;
|
||||
else
|
||||
this->max_bytes = bytes_per_second * kThrottleTimespanMs / 1000;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) {
|
||||
if(this->max_bytes <= 0) return false;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(this->max_bytes <= 0) return false; /* we've to test here again, else out arithmetic will fail */
|
||||
if(this->current_index != current_span) return false;
|
||||
if(this->bytes_send < this->max_bytes) return false;
|
||||
|
||||
next_timestamp.tv_usec = (kThrottleTimespanMs - current_ms % kThrottleTimespanMs) * 1000;
|
||||
next_timestamp.tv_sec = next_timestamp.tv_usec / 1000000;
|
||||
next_timestamp.tv_usec -= next_timestamp.tv_sec * 1000000;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t bytes_left() const {
|
||||
if(this->max_bytes <= 0) return (size_t) -1;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(this->max_bytes <= 0) return false; /* we've to test here again, else out arithmetic will fail */
|
||||
if(this->current_index != current_span) return this->max_bytes;
|
||||
if(this->bytes_send < this->max_bytes) return this->max_bytes - this->bytes_send;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds expected_writing_time(size_t bytes) const {
|
||||
std::lock_guard slock{this->mutex};
|
||||
|
||||
if(this->max_bytes <= 0) return std::chrono::milliseconds{0};
|
||||
return std::chrono::seconds{bytes / (this->max_bytes * (1000 / kThrottleTimespanMs))};
|
||||
}
|
||||
};
|
||||
|
||||
struct DualNetworkThrottle {
|
||||
NetworkThrottle *left, *right;
|
||||
|
||||
explicit DualNetworkThrottle(NetworkThrottle* left, NetworkThrottle* right) : left{left}, right{right} {
|
||||
assert(left);
|
||||
assert(right);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t bytes_left() const {
|
||||
return std::min(this->left->bytes_left(), this->right->bytes_left());
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds expected_writing_time(size_t bytes) const {
|
||||
return std::max(this->left->expected_writing_time(bytes), this->right->expected_writing_time(bytes));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) const {
|
||||
bool throttle = false;
|
||||
timeval right_timestamp{};
|
||||
throttle |= this->left->should_throttle(next_timestamp);
|
||||
throttle |= this->right->should_throttle(right_timestamp);
|
||||
if(!throttle) return false;
|
||||
|
||||
if(right_timestamp.tv_sec > next_timestamp.tv_sec || (right_timestamp.tv_sec == next_timestamp.tv_sec && right_timestamp.tv_usec > next_timestamp.tv_usec))
|
||||
next_timestamp = right_timestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool increase_bytes(size_t bytes) {
|
||||
bool result = false;
|
||||
result |= this->left->increase_bytes(bytes);
|
||||
result |= this->right->increase_bytes(bytes);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct TransferStatistics {
|
||||
constexpr static auto kMeasureTimespanMs{1000};
|
||||
constexpr static auto kAverageTimeCount{60};
|
||||
typedef uint8_t span_t;
|
||||
|
||||
size_t total_bytes{0};
|
||||
size_t delta_bytes{0}; /* used for statistics propagation */
|
||||
|
||||
span_t span_index{0};
|
||||
size_t span_bytes{0};
|
||||
std::array<size_t, kAverageTimeCount> history{};
|
||||
|
||||
spin_mutex mutex{};
|
||||
|
||||
inline void increase_bytes(size_t bytes) {
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kMeasureTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
this->total_bytes += bytes;
|
||||
|
||||
if(this->span_index != current_span)
|
||||
this->history[this->span_index % kAverageTimeCount] = std::exchange(this->span_bytes, 0);
|
||||
this->span_index = current_span;
|
||||
this->span_bytes += bytes;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t take_delta() {
|
||||
std::lock_guard slock{this->mutex};
|
||||
assert(this->delta_bytes <= this->total_bytes);
|
||||
auto delta = this->total_bytes - this->delta_bytes;
|
||||
this->delta_bytes = this->total_bytes;
|
||||
return delta;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline double current_bandwidth() const {
|
||||
return (this->history[(this->span_index - 1) % kAverageTimeCount] * (double) 1000) / (double) kMeasureTimespanMs;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline double average_bandwidth() const {
|
||||
return (std::accumulate(this->history.begin(), this->history.end(), 0UL) * (double) 1000) / (double) (kMeasureTimespanMs * kAverageTimeCount);
|
||||
}
|
||||
};
|
||||
}
|
||||
+63
-2
@@ -8,6 +8,8 @@
|
||||
#include <experimental/filesystem>
|
||||
#include <local_server/clnpath.h>
|
||||
#include <event2/thread.h>
|
||||
#include <include/files/Config.h>
|
||||
#include <local_server/HTTPUtils.h>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
@@ -55,8 +57,50 @@ inline void print_query(const std::string& message, const file::filesystem::Abst
|
||||
logWarning(LOG_FT, "{}: Unknown response state ({})!", message, (int) response.status);
|
||||
}
|
||||
|
||||
EVP_PKEY* ssl_generate_key() {
|
||||
auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
|
||||
|
||||
auto rsa = RSA_new();
|
||||
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(BN_new(), ::BN_free);
|
||||
BN_set_word(e.get(), RSA_F4);
|
||||
if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return nullptr;
|
||||
EVP_PKEY_assign_RSA(key.get(), rsa);
|
||||
return key.release();
|
||||
}
|
||||
|
||||
X509* ssl_generate_certificate(EVP_PKEY* key) {
|
||||
auto cert = X509_new();
|
||||
X509_set_pubkey(cert, key);
|
||||
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(cert), 3);
|
||||
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);
|
||||
|
||||
X509_NAME* name = nullptr;
|
||||
name = X509_get_subject_name(cert);
|
||||
//for(const auto& subject : this->subjects)
|
||||
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
|
||||
X509_set_subject_name(cert, name);
|
||||
|
||||
name = X509_get_issuer_name(cert);
|
||||
//for(const auto& subject : this->issues)
|
||||
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
|
||||
|
||||
X509_set_issuer_name(cert, name);
|
||||
|
||||
X509_sign(cert, key, EVP_sha512());
|
||||
return cert;
|
||||
}
|
||||
|
||||
int main() {
|
||||
evthread_use_pthreads();
|
||||
{
|
||||
std::map<std::string, std::string> query{};
|
||||
http::parse_url_parameters("http://www.example.org/suche?stichwort=wiki&no-arg&1arg=&ausgabe=liste&test=test#bla=d&blub=c", query);
|
||||
for(const auto& [key, value] : query)
|
||||
std::cout << key << " => " << value << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto log_config = std::make_shared<logger::LoggerConfig>();
|
||||
log_config->terminalLevel = spdlog::level::trace;
|
||||
@@ -71,6 +115,23 @@ int main() {
|
||||
auto instance = file::server();
|
||||
|
||||
|
||||
{
|
||||
auto options = std::make_shared<pipes::SSL::Options>();
|
||||
options->verbose_io = true;
|
||||
options->context_method = SSLv23_method();
|
||||
options->free_unused_keypairs = false;
|
||||
|
||||
{
|
||||
std::shared_ptr<EVP_PKEY> pkey{ssl_generate_key(), ::EVP_PKEY_free};
|
||||
std::shared_ptr<X509> cert{ssl_generate_certificate(&*pkey), ::X509_free};
|
||||
|
||||
options->default_keypair({pkey, cert});
|
||||
}
|
||||
file::config::ssl_option_supplier = [options]{
|
||||
return options;
|
||||
};
|
||||
}
|
||||
|
||||
#if 0
|
||||
auto& fs = instance->file_system();
|
||||
{
|
||||
@@ -140,7 +201,7 @@ int main() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
#if 0
|
||||
auto& ft = instance->file_transfer();
|
||||
|
||||
ft.callback_transfer_finished = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
|
||||
@@ -151,7 +212,7 @@ int main() {
|
||||
logMessage(0, "Transfer started");
|
||||
};
|
||||
|
||||
ft.callback_transfer_aborted = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const file::transfer::TransferError& error) {
|
||||
ft.callback_transfer_aborted = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const transfer::TransferStatistics&, const file::transfer::TransferError& error) {
|
||||
logMessage(0, "Transfer aborted ({}/{})", (int) error.error_type, error.error_message);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Test HTTPS file upload!
|
||||
+1
-1
Submodule git-teaspeak updated: be826ccb50...3133288d21
@@ -18,7 +18,7 @@ using namespace license;
|
||||
|
||||
/*
|
||||
* Requests/license: SELECT `tmp`.`keyId`, `tmp`.`key`, `tmp`.`ip`, `tmp`.`count`, `license_info`.`username`, `tmp`.`type` FROM (SELECT DISTINCT `license_request`.`keyId`, `key`, `license_request`.`ip`, COUNT(`license_request`.`keyId`) AS `count`, `license`.`type` FROM `license_request` INNER JOIN `license` ON `license`.`keyId` = `license_request`.`keyId` GROUP BY (`license_request`.`ip`)) AS `tmp` INNER JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId`
|
||||
* Different IP's: SELECT `tmp`.`keyId`, `license_info`.`username`, COUNT(`ip`) FROM (SELECT DISTINCT `ip`, `keyId` FROM `license_request`) AS `tmp` LEFT JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` GROUP BY (`tmp`.`keyId`)
|
||||
* Different IP's: SELECT `tmp`.`keyId`, `license_info`.`username`, COUNT(`ip`) FROM (SELECT DISTINCT `ip`, `keyId` FROM `license_request`) AS `tmp` LEFT JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` GROUP BY (`tmp`.`keyId`) ORDER BY COUNT(`ip`) DESC
|
||||
*
|
||||
* Requests (license) / ip: SELECT DISTINCT `ip`, COUNT(`ip`) FROM `license_request` WHERE `keyId` = ? GROUP BY `ip`
|
||||
*
|
||||
@@ -26,6 +26,8 @@ using namespace license;
|
||||
* //462
|
||||
*
|
||||
* SELECT DISTINCT(`ip`), `license_request`.`keyId`, `license_info`.`username` FROM `license_request` LEFT JOIN `license_info` ON `license_info`.`keyId` = `license_request`.`keyId` WHERE `timestamp` > (UNIX_TIMESTAMP() - 2 * 60 * 60) * 1000
|
||||
* License too many request looking: SELECT `keyId`, `username`, COUNT(`ip`) FROM `unique_license_requests` GROUP BY `keyId` ORDER BY COUNT(`ip`) DESC
|
||||
*
|
||||
*
|
||||
*
|
||||
* Online clients: SELECT SUM(`clients`) FROM (SELECT DISTINCT(`ip`), `clients` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a`
|
||||
@@ -34,6 +36,18 @@ using namespace license;
|
||||
*
|
||||
* Empty instances: curl -ik "https://stats.teaspeak.de:27788?type=request&request_type=general" -X GET 2>&1 | grep "data:"
|
||||
*/
|
||||
//SELECT SUM(`clients`) FROM `history_online` WHERE `keyId` = 701 OR `keyId` = 795 OR `keyId` = 792 OR `keyId` = 582 OR `keyId` = 753 OR `keyId` = 764 OR `keyId` = 761 OR `keyId` = 796 WHERE `timestamp` > (UNIX_TIMESTAMP() - 2.1 * 60 * 60) * 1000
|
||||
|
||||
/*
|
||||
* Extra users:
|
||||
* - ServerSponsoring
|
||||
* - Davide550
|
||||
* - xDeyego?
|
||||
* - latters
|
||||
* - Pamonha
|
||||
* - vova3639 (5 licenses)
|
||||
*/
|
||||
|
||||
bool handle_command(string& line);
|
||||
|
||||
shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
|
||||
+1
-1
Submodule music updated: 8e1ce32ae0...a4dabbb5c4
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "TeaSpeak",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^14.11.2",
|
||||
"yaml": "^1.10.0"
|
||||
}
|
||||
}
|
||||
+25
-9
@@ -43,15 +43,15 @@ set(SERVER_SOURCE_FILES
|
||||
# MySQLLibSSLFix.c
|
||||
|
||||
src/client/ConnectedClient.cpp
|
||||
src/client/voice/PrecomputedPuzzles.cpp
|
||||
src/server/PrecomputedPuzzles.cpp
|
||||
src/client/voice/VoiceClient.cpp
|
||||
src/client/voice/VoiceClientHandschake.cpp
|
||||
src/client/voice/VoiceClientCommandHandler.cpp
|
||||
src/client/voice/VoiceClientPacketHandler.cpp
|
||||
src/client/voice/VoiceClientView.cpp
|
||||
src/client/voice/VoiceClientConnectionPacketHandler.cpp
|
||||
src/client/voice/PacketStatistics.cpp
|
||||
src/TS3ServerClientManager.cpp
|
||||
src/VirtualServer.cpp
|
||||
src/FileServerHandler.cpp
|
||||
src/TS3ServerHeartbeat.cpp
|
||||
src/SignalHandler.cpp
|
||||
src/server/VoiceServer.cpp
|
||||
@@ -66,11 +66,8 @@ set(SERVER_SOURCE_FILES
|
||||
|
||||
src/client/ConnectedClientNotifyHandler.cpp
|
||||
src/VirtualServerManager.cpp
|
||||
src/server/file/LocalFileServer.cpp
|
||||
src/channel/ServerChannel.cpp
|
||||
src/channel/ClientChannelView.cpp
|
||||
src/client/file/FileClient.cpp
|
||||
src/client/file/FileClientIO.cpp
|
||||
src/Group.cpp
|
||||
src/manager/BanManager.cpp
|
||||
src/client/InternalClient.cpp
|
||||
@@ -97,7 +94,6 @@ set(SERVER_SOURCE_FILES
|
||||
src/manager/LetterManager.cpp
|
||||
src/manager/PermissionNameManager.cpp
|
||||
|
||||
src/pinteraction/ApplicationInteraction.cpp
|
||||
src/ServerManagerSnapshot.cpp
|
||||
src/ServerManagerSnapshotDeploy.cpp
|
||||
src/client/music/Song.cpp
|
||||
@@ -143,10 +139,28 @@ set(SERVER_SOURCE_FILES
|
||||
src/snapshots/server.cpp
|
||||
src/snapshots/groups.cpp
|
||||
src/snapshots/deploy.cpp
|
||||
src/snapshots/music.cpp
|
||||
src/snapshots/parser.cpp
|
||||
|
||||
src/manager/ActionLogger.cpp
|
||||
src/manager/ActionLoggerImpl.cpp
|
||||
|
||||
src/manager/ConversationManager.cpp
|
||||
src/client/SpeakingClientHandshake.cpp
|
||||
src/client/command_handler/music.cpp src/client/command_handler/file.cpp)
|
||||
src/client/command_handler/music.cpp src/client/command_handler/file.cpp
|
||||
|
||||
src/client/voice/PacketDecoder.cpp
|
||||
src/client/voice/PacketEncoder.cpp
|
||||
src/client/voice/ServerCommandExecutor.cpp
|
||||
src/client/voice/PingHandler.cpp
|
||||
src/client/voice/CryptSetupHandler.cpp
|
||||
|
||||
src/terminal/PipedTerminal.cpp
|
||||
|
||||
src/server/voice/UDPVoiceServer.cpp
|
||||
src/server/voice/DatagramPacket.cpp
|
||||
)
|
||||
|
||||
if (COMPILE_WEB_CLIENT)
|
||||
add_definitions(-DCOMPILE_WEB_CLIENT)
|
||||
|
||||
@@ -243,7 +257,7 @@ target_link_libraries(PermMapHelper
|
||||
|
||||
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
|
||||
SET(CPACK_PACKAGE_VERSION_MINOR "4")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "14")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "22")
|
||||
if (BUILD_TYPE_NAME EQUAL OFF)
|
||||
SET(CPACK_PACKAGE_VERSION_DATA "beta")
|
||||
elseif (BUILD_TYPE_NAME STREQUAL "")
|
||||
@@ -265,6 +279,7 @@ target_link_libraries(TeaSpeakServer
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
CXXTerminal::static #Static
|
||||
TeaSpeak-FileServer
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${YAML_CPP_LIBRARIES}
|
||||
pthread
|
||||
@@ -286,6 +301,7 @@ target_link_libraries(TeaSpeakServer
|
||||
|
||||
jsoncpp_lib
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
zstd::libzstd_static
|
||||
)
|
||||
|
||||
if (COMPILE_WEB_CLIENT)
|
||||
|
||||
+132
-4
@@ -1,3 +1,4 @@
|
||||
#include <dlfcn.h>
|
||||
#include <client/linux/handler/exception_handler.h>
|
||||
#include <iostream>
|
||||
#include <misc/strobf.h>
|
||||
@@ -10,7 +11,6 @@
|
||||
#include "src/VirtualServer.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "src/server/QueryServer.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "src/terminal/CommandHandler.h"
|
||||
#include "src/client/InternalClient.h"
|
||||
#include "src/SignalHandler.h"
|
||||
@@ -45,6 +45,7 @@ extern void testTomMath();
|
||||
#endif
|
||||
|
||||
#include <codecvt>
|
||||
#include <src/terminal/PipedTerminal.h>
|
||||
#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h"
|
||||
|
||||
class CLIParser {
|
||||
@@ -113,6 +114,82 @@ int main(int argc, char** argv) {
|
||||
(void*) malloc_conf;
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
{
|
||||
//ts::property::list<ts::property::InstanceProperties>()
|
||||
std::cout << "| Name | Type | Flags | Default Value | Description | \n";
|
||||
std::cout << "|:-- | -- | -- | -- |:-- | \n";
|
||||
for(const auto& property : ts::property::list<ts::property::VirtualServerProperties>()) {
|
||||
std::cout << "| `" << property->name << "` | ";
|
||||
|
||||
switch(property->type_value) {
|
||||
case ts::property::TYPE_STRING:
|
||||
std::cout << "String | ";
|
||||
break;
|
||||
|
||||
case ts::property::TYPE_BOOL:
|
||||
std::cout << "Boolean | ";
|
||||
break;
|
||||
|
||||
case ts::property::TYPE_SIGNED_NUMBER:
|
||||
std::cout << "Signed number | ";
|
||||
break;
|
||||
|
||||
case ts::property::TYPE_UNSIGNED_NUMBER:
|
||||
std::cout << "Unsigned number | ";
|
||||
break;
|
||||
|
||||
case ts::property::TYPE_FLOAT:
|
||||
std::cout << "Float | ";
|
||||
break;
|
||||
|
||||
default:
|
||||
std::cout << "Unknown | ";
|
||||
break;
|
||||
}
|
||||
|
||||
std::string flags{};
|
||||
if(property->flags & ts::property::FLAG_INTERNAL) { flags += ", internal"; }
|
||||
if(property->flags & ts::property::FLAG_GLOBAL) { flags += ", global"; }
|
||||
|
||||
if(property->flags & ts::property::FLAG_SNAPSHOT) { flags += ", snapshot"; }
|
||||
if(property->flags & ts::property::FLAG_SAVE) { flags += ", saved"; }
|
||||
if(property->flags & ts::property::FLAG_SAVE_MUSIC) { flags += ", saved (music)"; }
|
||||
|
||||
if(property->flags & ts::property::FLAG_NEW) { flags += ", new"; }
|
||||
if(property->flags & ts::property::FLAG_SERVER_VARIABLE) { flags += ", server variable"; }
|
||||
if(property->flags & ts::property::FLAG_SERVER_VIEW) { flags += ", server view variable"; }
|
||||
|
||||
if(property->flags & ts::property::FLAG_CLIENT_VARIABLE) { flags += ", client variable"; }
|
||||
if(property->flags & ts::property::FLAG_CLIENT_VIEW) { flags += ", client view variable"; }
|
||||
if(property->flags & ts::property::FLAG_CLIENT_INFO) { flags += ", client info variable"; }
|
||||
|
||||
if(property->flags & ts::property::FLAG_CHANNEL_VARIABLE) { flags += ", channel variable"; }
|
||||
if(property->flags & ts::property::FLAG_CHANNEL_VIEW) { flags += ", channel view variable"; }
|
||||
|
||||
// FLAG_GROUP_VIEW = FLAG_CHANNEL_VIEW << 1UL,
|
||||
if(property->flags & ts::property::FLAG_INSTANCE_VARIABLE) { flags += ", instance variable"; }
|
||||
if(property->flags & ts::property::FLAG_PLAYLIST_VARIABLE) { flags += ", playlist variable"; }
|
||||
if(property->flags & ts::property::FLAG_USER_EDITABLE) { flags += ", editable"; }
|
||||
|
||||
if(!flags.empty()) {
|
||||
std::cout << flags.substr(2);
|
||||
}
|
||||
std::cout << "| ";
|
||||
if(property->default_value.empty()) {
|
||||
std::cout << "empty";
|
||||
} else {
|
||||
std::cout << "`" << property->default_value << "` ";
|
||||
}
|
||||
std::cout << "| ";
|
||||
std::cout << "No description ";
|
||||
std::cout << "| ";
|
||||
std::cout << " \n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
CLIParser arguments(argc, argv);
|
||||
SSL_load_error_strings();
|
||||
OpenSSL_add_ssl_algorithms();
|
||||
@@ -126,7 +203,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
if(!arguments.cmdOptionExists("--no-terminal")) {
|
||||
terminal::install();
|
||||
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
|
||||
if(!terminal::active()) { cerr << "could not setup terminal!" << endl; return -1; }
|
||||
}
|
||||
|
||||
if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) {
|
||||
@@ -230,7 +307,6 @@ int main(int argc, char** argv) {
|
||||
if(true) return 0;
|
||||
*/
|
||||
|
||||
//debugMessage(LOG_GENERAL, "Sizeof ViewEntry {} Sizeof LinkedTreeEntry {} Sizeof shared_ptr<ViewEntry> {} Sizeof ClientChannelView {}", sizeof(ts::ViewEntry), sizeof(ts::TreeView::LinkedTreeEntry), sizeof(shared_ptr<ts::ViewEntry>), sizeof(ts::ClientChannelView));
|
||||
{
|
||||
//http://git.mcgalaxy.de/WolverinDEV/tomcrypt/blob/develop/src/misc/crypt/crypt_inits.c#L40-86
|
||||
std::string descriptors = "LTGE";
|
||||
@@ -406,6 +482,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
terminal::initialize_pipe(arguments.get_option("--pipe-path"));
|
||||
if(terminal::instance()) terminal::instance()->setPrompt("§7> §f");
|
||||
while(mainThreadActive) {
|
||||
usleep(5 * 1000);
|
||||
@@ -413,11 +490,23 @@ int main(int argc, char** argv) {
|
||||
if(terminal::instance()) {
|
||||
if(terminal::instance()->linesAvailable() > 0){
|
||||
while(!(line = terminal::instance()->readLine("§7> §f")).empty())
|
||||
threads::Thread(THREAD_DETACHED, [line](){ terminal::chandler::handleCommand(line); });
|
||||
threads::Thread(THREAD_DETACHED, [line]{
|
||||
terminal::chandler::CommandHandle handle{};
|
||||
handle.command = line;
|
||||
|
||||
if(!terminal::chandler::handleCommand(handle)) {
|
||||
for(const auto& response : handle.response)
|
||||
logErrorFmt(true, LOG_GENERAL, "{}", response);
|
||||
} else {
|
||||
for(const auto& response : handle.response)
|
||||
logMessageFmt(true, LOG_GENERAL, "{}", response);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
terminal::finalize_pipe();
|
||||
|
||||
stopApp:
|
||||
logMessageFmt(true, LOG_GENERAL, "Stopping application");
|
||||
@@ -439,4 +528,43 @@ int main(int argc, char** argv) {
|
||||
terminal::uninstall();
|
||||
mainThreadDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fix for Virtuzzo 7 where sometimes the pthread create fails! */
|
||||
|
||||
typedef int (*pthread_create_t)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
|
||||
pthread_create_t original_pthread_create{nullptr};
|
||||
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) {
|
||||
if(!original_pthread_create) {
|
||||
original_pthread_create = (pthread_create_t) dlsym(RTLD_NEXT, "pthread_create");
|
||||
if(!original_pthread_create) {
|
||||
std::cerr << "[CRITICAL] Missing original pthread_create function. Aborting execution!" << std::endl;
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
int result, attempt{0}, sleep{5};
|
||||
while((result = original_pthread_create(thread, attr, start_routine, arg)) != 0 && errno == EAGAIN) {
|
||||
if(attempt > 55) {
|
||||
std::cerr << "[CRITICAL] pthread_create(...) cause EAGAIN for the last 50 attempts (~4.7seconds)! Aborting application execution!" << std::endl;
|
||||
std::abort();
|
||||
} else if(attempt > 5) {
|
||||
/* let some other threads do work */
|
||||
pthread_yield();
|
||||
} else if(attempt == 0) {
|
||||
std::string message{"[CRITICAL] Failed to spawn thread (Resource temporarily unavailable). Trying to recover."};
|
||||
std::cerr << message << std::endl;
|
||||
}
|
||||
|
||||
//std::string message{"[CRITICAL] pthread_create(...) cause EAGAIN! Trying again in " + std::to_string(sleep) + "usec (Attempt: " + std::to_string(attempt) + ")"};
|
||||
//std::cerr << message << std::endl;
|
||||
usleep(sleep);
|
||||
attempt++;
|
||||
sleep = (int) (sleep * 1.25);
|
||||
}
|
||||
if(attempt > 0) {
|
||||
std::string message{"[CRITICAL] Successfully recovered from pthread_create() EAGAIN error. Took " + std::to_string(attempt) + " attempts."};
|
||||
std::cerr << message << std::endl;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <utility>
|
||||
|
||||
#include <log/LogUtils.h>
|
||||
#include "Configuration.h"
|
||||
#include "build.h"
|
||||
#include "../../license/shared/include/license/license.h"
|
||||
@@ -46,6 +47,16 @@ bool config::server::strict_ut8_mode;
|
||||
bool config::server::show_invisible_clients_as_online;
|
||||
bool config::server::disable_ip_saving;
|
||||
bool config::server::default_music_bot;
|
||||
/*
|
||||
* namespace limits {
|
||||
extern size_t poke_message_length;
|
||||
extern size_t talk_power_request_message_length;
|
||||
}
|
||||
*/
|
||||
size_t config::server::limits::poke_message_length;
|
||||
size_t config::server::limits::talk_power_request_message_length;
|
||||
size_t config::server::limits::afk_message_length;
|
||||
|
||||
ssize_t config::server::max_virtual_server;
|
||||
bool config::server::badges::allow_badges;
|
||||
bool config::server::badges::allow_overwolf;
|
||||
@@ -566,6 +577,14 @@ std::vector<std::string> config::reload() {
|
||||
|
||||
auto bindings = create_bindings();
|
||||
read_bindings(config, bindings, FLAG_RELOADABLE);
|
||||
|
||||
|
||||
const auto& logConfig = logger::currentConfig();
|
||||
if(logConfig) {
|
||||
logConfig->logfileLevel = (spdlog::level::level_enum) ts::config::log::logfileLevel;
|
||||
logConfig->terminalLevel = (spdlog::level::level_enum) ts::config::log::terminalLevel;
|
||||
logger::updateLogLevels();
|
||||
}
|
||||
} catch(const YAML::Exception& ex) {
|
||||
errors.emplace_back("Could not read config: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column));
|
||||
return errors;
|
||||
@@ -1007,8 +1026,9 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
{
|
||||
BIND_GROUP(log)
|
||||
{
|
||||
CREATE_BINDING("level", 0);
|
||||
CREATE_BINDING("level", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::log::logfileLevel, spdlog::level::debug, spdlog::level::trace, spdlog::level::off);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
ADD_DESCRIPTION("The log level within the log files");
|
||||
ADD_DESCRIPTION("Available types:");
|
||||
ADD_DESCRIPTION(" 0: Trace");
|
||||
@@ -1020,8 +1040,9 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_DESCRIPTION(" 6: Off");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("terminal_level", 0);
|
||||
CREATE_BINDING("terminal_level", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::log::terminalLevel, spdlog::level::info, spdlog::level::trace, spdlog::level::off);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
ADD_DESCRIPTION("The log level within the TeaSpeak server terminal");
|
||||
ADD_DESCRIPTION("Available types:");
|
||||
ADD_DESCRIPTION(" 0: Trace");
|
||||
@@ -1148,17 +1169,17 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
"The start point for the server creation still apply.");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("notifymute", 0);
|
||||
CREATE_BINDING("notifymute", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::voice::notifyMuted, false);
|
||||
ADD_DESCRIPTION("Enable/disable the mute notify");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("suppress_myts_warnings", 0);
|
||||
CREATE_BINDING("suppress_myts_warnings", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::voice::suppress_myts_warnings, true);
|
||||
ADD_DESCRIPTION("Suppress the MyTS integration warnings");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("allow_session_reinitialize", 0);
|
||||
CREATE_BINDING("allow_session_reinitialize", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::voice::allow_session_reinitialize, true);
|
||||
ADD_DESCRIPTION("Enable/disable fast session reinitialisation.");
|
||||
ADD_SENSITIVE();
|
||||
@@ -1192,19 +1213,19 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
}
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("connect_limit", 0);
|
||||
CREATE_BINDING("connect_limit", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::voice::connectLimit, 10, 0, 1024);
|
||||
ADD_DESCRIPTION("Maximum amount of join attempts per second.");
|
||||
ADD_NOTE("A value of zero means unlimited");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("client_connect_limit", 0);
|
||||
CREATE_BINDING("client_connect_limit", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::voice::clientConnectLimit, 3, 0, 1024);
|
||||
ADD_DESCRIPTION("Maximum amount of join attempts per second per ip.");
|
||||
ADD_NOTE("A value of zero means unlimited");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("protocol.experimental_31", 0);
|
||||
CREATE_BINDING("protocol.experimental_31", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::experimental_31, false);
|
||||
ADD_DESCRIPTION("Enables the newer and safer protocol based on TeamSpeak's documented standard");
|
||||
ADD_NOTE("An invalid protocol chain could lead clients to calculate a wrong shared secret result");
|
||||
@@ -1249,24 +1270,26 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
BIND_BOOL(config::server::delete_old_bans, true);
|
||||
ADD_DESCRIPTION("Enable/disable the deletion of old bans within the database");
|
||||
}
|
||||
#if 0
|
||||
{
|
||||
CREATE_BINDING("delete_missing_icon_permissions", 0);
|
||||
BIND_BOOL(config::server::delete_missing_icon_permissions, true);
|
||||
ADD_DESCRIPTION("Enable/disable the deletion of invalid icon id permissions");
|
||||
}
|
||||
#endif
|
||||
{
|
||||
CREATE_BINDING("allow_weblist", 0);
|
||||
BIND_BOOL(config::server::enable_teamspeak_weblist, true);
|
||||
ADD_DESCRIPTION("Enable/disable weblist reports globally! (Server setting wount be disabled, they will be just not send)");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("strict_ut8_mode", 0);
|
||||
CREATE_BINDING("strict_ut8_mode", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::strict_ut8_mode, false);
|
||||
ADD_DESCRIPTION("If enabled an error will be throws on invalid UTF-8 characters within the protocol (Query & Client).");
|
||||
ADD_DESCRIPTION("Else the property pair will be dropped silently!");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("show_invisible_clients", 0);
|
||||
CREATE_BINDING("show_invisible_clients", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::show_invisible_clients_as_online, true);
|
||||
ADD_DESCRIPTION("Allow anybody to send text messages to clients which are in invisible channels");
|
||||
}
|
||||
@@ -1276,7 +1299,7 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_DESCRIPTION("Disable the saving of IP addresses within the database.");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("default_music_bot", 0);
|
||||
CREATE_BINDING("default_music_bot", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::default_music_bot, true);
|
||||
ADD_DESCRIPTION("Add by default a new music bot to each created virtual server.");
|
||||
}
|
||||
@@ -1286,6 +1309,27 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_DESCRIPTION("Set the limit for maximal virtual servers. -1 means unlimited.");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
BIND_GROUP(limits);
|
||||
|
||||
{
|
||||
CREATE_BINDING("poke_message_length", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::limits::poke_message_length, 1024, 1, 262144);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
|
||||
{
|
||||
CREATE_BINDING("talk_power_request_message_length", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::limits::talk_power_request_message_length, 50, 1, 262144);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
|
||||
{
|
||||
CREATE_BINDING("afk_message_length", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::limits::afk_message_length, 50, 1, 262144);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
}
|
||||
{
|
||||
/*
|
||||
BIND_GROUP(badges);
|
||||
|
||||
@@ -78,6 +78,12 @@ namespace ts::config {
|
||||
extern bool disable_ip_saving;
|
||||
extern bool default_music_bot;
|
||||
|
||||
namespace limits {
|
||||
extern size_t poke_message_length;
|
||||
extern size_t talk_power_request_message_length;
|
||||
extern size_t afk_message_length;
|
||||
}
|
||||
|
||||
namespace badges {
|
||||
extern bool allow_overwolf;
|
||||
extern bool allow_badges;
|
||||
|
||||
+351
-191
@@ -13,146 +13,153 @@ using namespace ts::permission;
|
||||
|
||||
//#define DISABLE_CACHING
|
||||
|
||||
struct ts::server::CachedPermissionManager {
|
||||
ServerId server_id{0};
|
||||
ClientDbId client_database_id{0};
|
||||
std::weak_ptr<permission::v2::PermissionManager> instance{};
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> instance_ref{}; /* reference to the current instance, will be refreshed every time the instance gets accessed */
|
||||
std::chrono::time_point<std::chrono::system_clock> last_access{};
|
||||
};
|
||||
|
||||
|
||||
struct ts::server::StartupCacheEntry {
|
||||
ServerId sid{0};
|
||||
|
||||
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions{};
|
||||
std::deque<std::unique_ptr<StartupPropertyEntry>> properties{};
|
||||
};
|
||||
|
||||
DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {}
|
||||
DatabaseHelper::~DatabaseHelper() {
|
||||
for(const auto& elm : cachedPermissionManagers)
|
||||
delete elm;
|
||||
cachedPermissionManagers.clear();
|
||||
this->cached_permission_managers.clear();
|
||||
}
|
||||
|
||||
void DatabaseHelper::tick() {
|
||||
auto cache_timeout = std::chrono::system_clock::now() - std::chrono::minutes{10};
|
||||
{
|
||||
threads::MutexLock l(this->permManagerLock);
|
||||
auto cpy = this->cachedPermissionManagers;
|
||||
for(const auto& mgr : cpy){
|
||||
//if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) //TODO remove instand delete!
|
||||
mgr->ownLock.reset();
|
||||
if(mgr->manager.expired()){
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), mgr));
|
||||
delete mgr;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::lock_guard cp_lock{this->cached_permission_manager_lock};
|
||||
|
||||
{
|
||||
threads::MutexLock l(this->propsLock);
|
||||
auto pcpy = this->cachedProperties;
|
||||
for(const auto& mgr : pcpy) {
|
||||
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
|
||||
mgr->ownLock.reset();
|
||||
if(mgr->properties.expired()) {
|
||||
this->cachedProperties.erase(std::find(this->cachedProperties.begin(), this->cachedProperties.end(), mgr));
|
||||
delete mgr;
|
||||
}
|
||||
}
|
||||
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const std::unique_ptr<CachedPermissionManager>& manager) {
|
||||
if(manager->last_access < cache_timeout)
|
||||
manager->instance_ref = nullptr;
|
||||
|
||||
if(manager->instance.expired())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}), this->cached_permission_managers.end());
|
||||
}
|
||||
}
|
||||
|
||||
int collectData(deque<shared_ptr<ClientDatabaseInfo>>* list, int length, char** values, char** columns){
|
||||
shared_ptr<ClientDatabaseInfo> entry = std::make_shared<ClientDatabaseInfo>();
|
||||
constexpr static std::string_view kSqlBase{"SELECT `client_unique_id`, `client_database_id`, `client_nickname`, `client_created`, `client_last_connected`, `client_total_connections` FROM `clients_server`"};
|
||||
inline std::deque<std::shared_ptr<ClientDatabaseInfo>> query_database_client_info(sql::SqlManager* sql_manager, ServerId server_id, const std::string& query, const std::vector<variable>& variables) {
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> result{};
|
||||
|
||||
for(int index = 0; index < length; index++)
|
||||
if(strcmp(columns[index], "cldbid") == 0)
|
||||
entry->cldbid = static_cast<ClientDbId>(stol(values[index]));
|
||||
else if(strcmp(columns[index], "clientUid") == 0)
|
||||
entry->uniqueId = values[index];
|
||||
else if(strcmp(columns[index], "firstConnect") == 0)
|
||||
entry->created = time_point<system_clock>() + seconds(stoll(values[index]));
|
||||
else if(strcmp(columns[index], "lastConnect") == 0)
|
||||
entry->lastjoin = time_point<system_clock>() + seconds(stoll(values[index]));
|
||||
else if(strcmp(columns[index], "connections") == 0)
|
||||
entry->connections = static_cast<uint32_t>(stoi(values[index]));
|
||||
else if(strcmp(columns[index], "lastName") == 0)
|
||||
entry->lastName = values[index];
|
||||
else if(strcmp(columns[index], "serverId") == 0);
|
||||
else logError(LOG_GENERAL, "Invalid db key for manager data. Key: {}", columns[index]);
|
||||
sql::command command{sql_manager, query};
|
||||
for(const auto& variable : variables)
|
||||
command.value(variable);
|
||||
auto sql_result = command.query([&](int length, std::string* values, std::string* names) {
|
||||
auto entry = std::make_shared<ClientDatabaseInfo>();
|
||||
|
||||
list->push_back(entry);
|
||||
return 0;
|
||||
}
|
||||
auto index{0};
|
||||
try {
|
||||
assert(names[index] == "client_unique_id");
|
||||
entry->client_unique_id = values[index++];
|
||||
|
||||
#define MAX_QUERY 32
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
|
||||
if(list.empty()) return {};
|
||||
assert(names[index] == "client_database_id");
|
||||
entry->client_database_id = std::stoull(values[index++]);
|
||||
|
||||
deque<shared_ptr<ClientDatabaseInfo>> result;
|
||||
assert(names[index] == "client_nickname");
|
||||
entry->client_nickname = values[index++];
|
||||
|
||||
if(list.size() <= MAX_QUERY){
|
||||
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
|
||||
for(auto elm : list)
|
||||
query += " `cldbid` = " + to_string(elm) + " OR";
|
||||
query = query.substr(0, query.length() - 3) + ")";
|
||||
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfo(...) -> {}", query);
|
||||
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(std::function<decltype(collectData)>(collectData), &result);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(state);
|
||||
if(!state) return {};
|
||||
} else {
|
||||
std::deque<ClientDbId> sub;
|
||||
do {
|
||||
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
assert(names[index] == "client_created");
|
||||
entry->client_created = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
|
||||
|
||||
auto res = this->queryDatabaseInfo(server, sub);
|
||||
result.insert(result.end(), res.begin(), res.end());
|
||||
sub.clear();
|
||||
} while(!list.empty());
|
||||
assert(names[index] == "client_last_connected");
|
||||
entry->client_last_connected = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
entry->client_total_connections = std::stoull(values[index++]);
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
logError(server_id, "Failed to parse client base properties at index {}: {}. Query: {}",
|
||||
index - 1,
|
||||
ex.what(),
|
||||
query
|
||||
);
|
||||
}
|
||||
|
||||
result.push_back(std::move(entry));
|
||||
});
|
||||
if(!sql_result) {
|
||||
logError(server_id, "Failed to query client database infos: {}; Query: {}", sql_result.fmtStr(), query);
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
|
||||
if(list.empty())
|
||||
return {};
|
||||
|
||||
std::string valueList{};
|
||||
for(const auto& element : list)
|
||||
valueList += ", " + std::to_string(element);
|
||||
valueList = valueList.substr(2);
|
||||
|
||||
auto serverId = server ? server->getServerId() : 0;
|
||||
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_database_id` IN (" + valueList + ")", {variable{":sid", serverId}});
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &server, std::deque<std::string> list) {
|
||||
if(list.empty()) return {};
|
||||
if(list.empty())
|
||||
return {};
|
||||
|
||||
deque<shared_ptr<ClientDatabaseInfo>> result;
|
||||
std::string valueList{};
|
||||
for(size_t value_index{0}; value_index < list.size(); value_index++)
|
||||
valueList += ", :v" + std::to_string(value_index);
|
||||
valueList = valueList.substr(2);
|
||||
|
||||
if(list.size() <= MAX_QUERY){
|
||||
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
|
||||
for(const auto &elm : list)
|
||||
query += " `clientUid` = '" + elm + "' OR";
|
||||
query = query.substr(0, query.length() - 3) + ")";
|
||||
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfoByUid(...) -> {}", query);
|
||||
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(function<decltype(collectData)>(collectData), &result);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(state);
|
||||
if(!state) return {};
|
||||
} else {
|
||||
std::deque<std::string> sub;
|
||||
do {
|
||||
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
auto serverId = server ? server->getServerId() : 0;
|
||||
|
||||
auto res = this->queryDatabaseInfoByUid(server, sub);
|
||||
result.insert(result.end(), res.begin(), res.end());
|
||||
sub.clear();
|
||||
} while(!list.empty());
|
||||
}
|
||||
std::vector<variable> values{};
|
||||
values.reserve(list.size() + 1);
|
||||
|
||||
return result;
|
||||
values.emplace_back(":sid", serverId);
|
||||
for(size_t value_index{0}; value_index < list.size(); value_index++)
|
||||
values.emplace_back(":v" + std::to_string(value_index), list[value_index]);
|
||||
|
||||
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_unique_id` IN (" + valueList + ")", values);
|
||||
}
|
||||
|
||||
bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) { return cldbid > 0; } //TODO here check
|
||||
|
||||
void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
||||
ServerId sid = static_cast<ServerId>(server ? server->getServerId() : 0);
|
||||
auto serverId = (ServerId) (server ? server->getServerId() : 0);
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(permManagerLock);
|
||||
for(auto permMgr : this->cachedPermissionManagers)
|
||||
if(permMgr->cldbid == cldbid && permMgr->sid == sid) {
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
|
||||
delete permMgr;
|
||||
break;
|
||||
}
|
||||
lock_guard lock{cached_permission_manager_lock};
|
||||
|
||||
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
|
||||
return entry->server_id == serverId && entry->client_database_id == cldbid;
|
||||
}), this->cached_permission_managers.end());
|
||||
}
|
||||
//TODO remove from props cache?
|
||||
|
||||
sql::result state{};
|
||||
|
||||
auto state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", sid}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", sid}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", serverId}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", serverId}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
|
||||
|
||||
if(serverId == 0) {
|
||||
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
|
||||
} else {
|
||||
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `server_id` = :sid AND `client_database_id` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
|
||||
}
|
||||
|
||||
//TODO delete letters
|
||||
//TODO delete query
|
||||
@@ -230,26 +237,38 @@ inline sql::result load_permissions_v2(
|
||||
logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
|
||||
}
|
||||
|
||||
#define UPDATE_COMMAND "UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
|
||||
#define INSERT_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
|
||||
#define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
|
||||
constexpr static std::string_view kPermissionUpdateCommand{"UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
|
||||
constexpr static std::string_view kPermissionInsertCommand{"INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"};
|
||||
constexpr static std::string_view kPermissionDeleteCommand{"DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::find_cached_permission_manager(ServerId server_id,
|
||||
ClientDbId client_database_id) {
|
||||
for(auto it = this->cached_permission_managers.begin(); it != this->cached_permission_managers.end(); it++) {
|
||||
auto& cached_manager = *it;
|
||||
|
||||
if(cached_manager->client_database_id == client_database_id && cached_manager->server_id == server_id) {
|
||||
auto manager = cached_manager->instance.lock();
|
||||
if(!manager){
|
||||
this->cached_permission_managers.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
cached_manager->last_access = system_clock::now();
|
||||
cached_manager->instance_ref = manager;
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
#ifndef DISABLE_CACHING
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(permManagerLock);
|
||||
for(auto permMgr : this->cachedPermissionManagers)
|
||||
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)) {
|
||||
auto ptr = permMgr->manager.lock();
|
||||
if(!ptr){
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
|
||||
delete permMgr;
|
||||
break;
|
||||
}
|
||||
permMgr->lastAccess = system_clock::now();
|
||||
return ptr;
|
||||
}
|
||||
std::lock_guard lock{cached_permission_manager_lock};
|
||||
auto manager = this->find_cached_permission_manager(server_id, cldbid);
|
||||
if(manager) return manager;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -292,15 +311,23 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
|
||||
|
||||
|
||||
#ifndef DISABLE_CACHING
|
||||
this->permManagerLock.lock();
|
||||
auto entry = new CachedPermissionManager();
|
||||
entry->sid = server_id;
|
||||
entry->manager = permission_manager;
|
||||
entry->ownLock = permission_manager;
|
||||
entry->cldbid = cldbid;
|
||||
entry->lastAccess = system_clock::now();
|
||||
this->cachedPermissionManagers.push_back(entry);
|
||||
this->permManagerLock.unlock();
|
||||
auto cache_entry = std::make_unique<CachedPermissionManager>();
|
||||
cache_entry->server_id = server_id;
|
||||
cache_entry->instance = permission_manager;
|
||||
cache_entry->instance_ref = permission_manager;
|
||||
cache_entry->client_database_id = cldbid;
|
||||
cache_entry->last_access = system_clock::now();
|
||||
|
||||
{
|
||||
std::lock_guard cache_lock{this->cached_permission_manager_lock};
|
||||
|
||||
/* test if we might not got a second instance */
|
||||
auto manager = this->find_cached_permission_manager(server_id, cldbid);
|
||||
if(manager) return manager;
|
||||
|
||||
this->cached_permission_managers.push_back(std::move(cache_entry));
|
||||
}
|
||||
|
||||
#endif
|
||||
return permission_manager;
|
||||
}
|
||||
@@ -313,7 +340,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
@@ -380,7 +407,7 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::Virt
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
@@ -452,7 +479,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
@@ -518,7 +545,7 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
|
||||
@@ -560,52 +587,44 @@ std::shared_ptr<Properties> DatabaseHelper::default_properties_client(std::share
|
||||
return properties;
|
||||
}
|
||||
|
||||
std::mutex DatabaseHelper::database_id_mutex{};
|
||||
|
||||
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::shared_ptr<DataClient> cl) {
|
||||
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId serverId, std::shared_ptr<DataClient> cl) {
|
||||
cl->loadDataForCurrentServer();
|
||||
if(cl->getClientDatabaseId() == 0){ //Client does not exist
|
||||
ClientDbId new_client_database_id{0};
|
||||
auto res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 AND `clientUid` = :cluid", variable{":cluid", cl->getUid()}).query([](ClientDbId* ptr, int length, char** values, char** names){
|
||||
*ptr = static_cast<ClientDbId>(stoll(values[0]));
|
||||
return 0;
|
||||
}, &new_client_database_id);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(res);
|
||||
if(!res) return false;
|
||||
if(cl->getClientDatabaseId() == 0) {
|
||||
/* client does not exists, create a new one */
|
||||
|
||||
auto insertTemplate = sql::model(sql, "INSERT INTO `clients` (`serverId`, `cldbId`, `clientUid`, `lastName`,`firstConnect`,`lastConnect`, `connections`) VALUES (:serverId, :cldbid, :cluid, :name, :fconnect, :lconnect, :connections)",
|
||||
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()},
|
||||
variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0},
|
||||
variable{":connections", 0});
|
||||
if(new_client_database_id == 0) { /* we've a completely new user */
|
||||
std::lock_guard db_id_lock{DatabaseHelper::database_id_mutex};
|
||||
res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([&](int length, std::string* values, std::string* names) {
|
||||
assert(length == 1);
|
||||
new_client_database_id = (ClientDbId) stoll(values[0]);
|
||||
});
|
||||
LOG_SQL_CMD(res);
|
||||
if(!res) return false;
|
||||
sql::result sql_result{};
|
||||
|
||||
new_client_database_id += 1;
|
||||
res = insertTemplate.command().values(variable{":serverId", 0}, variable{":cldbid", new_client_database_id}).execute(); //Insert global
|
||||
LOG_SQL_CMD(res);
|
||||
if(!res) return false;
|
||||
debugMessage(LOG_INSTANCE, "Registered a new client. Unique id: {}, First server: {}, Database ID: {}", cl->getUid(), id, new_client_database_id);
|
||||
} else {
|
||||
debugMessage(id, "Having new client, which is already known on this instance. Unique id: {}, First server: {}, Database ID: {}", cl->getUid(), id, new_client_database_id);
|
||||
std::string insert_or_ignore{sql->getType() == sql::TYPE_SQLITE ? "INSERT OR IGNORE" : "INSERT IGNORE"};
|
||||
|
||||
auto currentTimeSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
sql_result = sql::command{sql, insert_or_ignore + " INTO `clients` (`client_unique_id`, `client_created`) VALUES (:uniqueId, :now)",
|
||||
variable{":uniqueId", cl->getUid()},
|
||||
variable{":now", currentTimeSeconds}
|
||||
}.execute();
|
||||
|
||||
if(!sql_result) {
|
||||
logCritical(LOG_INSTANCE, "Failed to execute client insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
if(id != 0){ //Else already inserted
|
||||
res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", new_client_database_id}).execute();
|
||||
pf(res);
|
||||
if(!res) return false;
|
||||
sql_result = sql::command{sql, "INSERT INTO `clients_server` (`server_id`, `client_unique_id`, `client_database_id`, `client_created`) SELECT :serverId, :uniqueId, `client_database_id`, :now FROM `clients` WHERE `client_unique_id` = :uniqueId;",
|
||||
variable{":serverId", serverId},
|
||||
variable{":uniqueId", cl->getUid()},
|
||||
variable{":now", currentTimeSeconds}
|
||||
}.execute();
|
||||
|
||||
if(!sql_result) {
|
||||
logCritical(LOG_INSTANCE, "Failed to execute client server insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
return assignDatabaseId(sql, id, cl);
|
||||
if(!cl->loadDataForCurrentServer())
|
||||
return false;
|
||||
|
||||
debugMessage(serverId, "Successfully registered client {} for server {} with database id {}.", cl->getUid(), serverId, cl->getClientDatabaseId());
|
||||
return true;
|
||||
}
|
||||
|
||||
logTrace(id, "Loaded client successfully from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -886,6 +905,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
if(server) {
|
||||
props->operator[](property::CLIENT_DESCRIPTION) = server->properties()[property::VIRTUALSERVER_DEFAULT_CLIENT_DESCRIPTION].value();
|
||||
}
|
||||
|
||||
bool loaded = false;
|
||||
if(use_startup_cache && server) {
|
||||
shared_ptr<StartupCacheEntry> entry;
|
||||
@@ -910,6 +930,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!loaded) {
|
||||
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type1", property::PropertyType::PROP_TYPE_CONNECTION}, variable{":type2", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", cldbid});
|
||||
|
||||
@@ -943,15 +964,15 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
if(!prop.hasDbReference() && (prop.default_value() == prop.value())) return; //No changes to default value
|
||||
prop.setModified(false);
|
||||
|
||||
std::string sql;
|
||||
std::string sqlCommand;
|
||||
if(prop.hasDbReference())
|
||||
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
|
||||
sqlCommand = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
|
||||
else {
|
||||
prop.setDbReference(true);
|
||||
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
|
||||
sqlCommand = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
|
||||
}
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + std::string{prop.type().name} + " value: " + prop.value());
|
||||
sql::command(this->sql, sql,
|
||||
sql::command(this->sql, sqlCommand,
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", prop.type().type_property},
|
||||
variable{":id", cldbid},
|
||||
@@ -967,16 +988,62 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
return;
|
||||
}
|
||||
|
||||
std::string query;
|
||||
if(prop.type() == property::CLIENT_TOTALCONNECTIONS)
|
||||
query = "UPDATE `clients` SET `connections` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_NICKNAME)
|
||||
query = "UPDATE `clients` SET `lastName` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_LASTCONNECTED)
|
||||
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
if(query.empty()) return;
|
||||
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + std::string{prop.type().name} + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")");
|
||||
sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":cldbid", cldbid}, variable{":value", prop.value()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
std::string column;
|
||||
if(prop.type().type_property == property::PROP_TYPE_CLIENT) {
|
||||
switch (prop.type().property_index) {
|
||||
case property::CLIENT_NICKNAME:
|
||||
column = "client_nickname";
|
||||
break;
|
||||
|
||||
case property::CLIENT_LASTCONNECTED:
|
||||
column = "client_last_connected";
|
||||
break;
|
||||
|
||||
case property::CLIENT_TOTALCONNECTIONS:
|
||||
column = "client_total_connections";
|
||||
break;
|
||||
|
||||
case property::CLIENT_MONTH_BYTES_UPLOADED:
|
||||
column = "client_month_upload";
|
||||
break;
|
||||
|
||||
case property::CLIENT_TOTAL_BYTES_UPLOADED:
|
||||
column = "client_total_upload";
|
||||
break;
|
||||
|
||||
case property::CLIENT_MONTH_BYTES_DOWNLOADED:
|
||||
column = "client_month_download";
|
||||
break;
|
||||
|
||||
case property::CLIENT_TOTAL_BYTES_DOWNLOADED:
|
||||
column = "client_total_download";
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else if(prop.type().type_property == property::PROP_TYPE_CONNECTION) {
|
||||
switch (prop.type().property_index) {
|
||||
case property::CONNECTION_CLIENT_IP:
|
||||
column = "client_ip";
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '{}' for {} (New value: {}, Column: {})",
|
||||
prop.type().name,
|
||||
cldbid,
|
||||
prop.value(),
|
||||
column
|
||||
);
|
||||
sql::command(this->sql, "UPDATE `clients_server` SET `" + column + "` = :value WHERE `server_id` = :serverId AND `client_database_id` = :cldbid",
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":cldbid", cldbid},
|
||||
variable{":value", prop.value()}
|
||||
).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
});
|
||||
|
||||
return props;
|
||||
@@ -1023,6 +1090,15 @@ void DatabaseHelper::clearStartupCache(ts::ServerId sid) {
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHelper::handleServerDelete(ServerId server_id) {
|
||||
{
|
||||
std::lock_guard pm_lock{this->cached_permission_manager_lock};
|
||||
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
|
||||
return entry->server_id == server_id;
|
||||
}), this->cached_permission_managers.end());
|
||||
}
|
||||
}
|
||||
|
||||
//SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`
|
||||
//SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`
|
||||
struct StartupPermissionArgument {
|
||||
@@ -1164,13 +1240,19 @@ void DatabaseHelper::loadStartupPropertyCache() {
|
||||
}, &arg);
|
||||
}
|
||||
|
||||
bool DatabaseHelper::deleteGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::GroupId group_id) {
|
||||
auto command = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", permission::SQL_PERM_GROUP},
|
||||
variable{":id", group_id}).execute();
|
||||
LOG_SQL_CMD(command);
|
||||
return !!command;
|
||||
void DatabaseHelper::deleteGroupArtifacts(ServerId server_id, GroupId group_id) {
|
||||
sql::result result{};
|
||||
|
||||
result = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
|
||||
variable{":serverId", server_id},
|
||||
variable{":type", permission::SQL_PERM_GROUP},
|
||||
variable{":id", group_id}).execute();
|
||||
LOG_SQL_CMD(result);
|
||||
|
||||
result = sql::command(this->sql, "DELETE FROM `tokens` WHERE `serverId` = :serverId AND `targetGroup` = :id",
|
||||
variable{":serverId", server_id},
|
||||
variable{":id", group_id}).execute();
|
||||
LOG_SQL_CMD(result);
|
||||
}
|
||||
|
||||
bool DatabaseHelper::deleteChannelPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ChannelId channel_id) {
|
||||
@@ -1211,4 +1293,82 @@ bool DatabaseHelper::deletePlaylist(const std::shared_ptr<ts::server::VirtualSer
|
||||
).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete playlist properties for playlist " + to_string(playlist_id)});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr static auto kDBListQuery{R"(
|
||||
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
|
||||
SELECT
|
||||
`clients_server`.`client_database_id`,
|
||||
`clients_server`.`client_unique_id`,
|
||||
`clients_server`.`client_nickname`,
|
||||
`clients_server`.`client_ip`,
|
||||
`clients_server`.`client_created`,
|
||||
`clients_server`.`client_last_connected`,
|
||||
`clients_server`.`client_total_connections`,
|
||||
`clients`.`client_login_name` FROM `clients_server`
|
||||
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
|
||||
WHERE `server_id` = :serverId LIMIT :offset, :limit
|
||||
) AS `clients`
|
||||
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
|
||||
)"};
|
||||
|
||||
void DatabaseHelper::listDatabaseClients(
|
||||
ServerId server_id,
|
||||
const std::optional<int64_t>& offset,
|
||||
const std::optional<int64_t>& limit,
|
||||
void (* callback)(void *, const DatabaseClient &),
|
||||
void *user_argument) {
|
||||
|
||||
DatabaseClient client;
|
||||
size_t set_index{0};
|
||||
auto sqlResult = sql::command{this->sql, kDBListQuery,
|
||||
variable{":serverId", server_id},
|
||||
variable{":offset", offset.has_value() ? *offset : 0},
|
||||
variable{":limit", limit.has_value() ? *limit : -1}
|
||||
}.query([&](int length, std::string* values, std::string* names) {
|
||||
set_index++;
|
||||
|
||||
auto index{0};
|
||||
try {
|
||||
assert(names[index] == "client_database_id");
|
||||
client.client_database_id = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_unique_id");
|
||||
client.client_unique_id = values[index++];
|
||||
|
||||
assert(names[index] == "client_nickname");
|
||||
client.client_nickname = values[index++];
|
||||
|
||||
assert(names[index] == "client_ip");
|
||||
client.client_ip = values[index++];
|
||||
|
||||
assert(names[index] == "client_created");
|
||||
client.client_created = values[index++];
|
||||
|
||||
assert(names[index] == "client_last_connected");
|
||||
client.client_last_connected = values[index++];
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
client.client_total_connections = values[index++];
|
||||
|
||||
assert(names[index] == "client_login_name");
|
||||
client.client_login_name = values[index++];
|
||||
|
||||
assert(names[index] == "client_description");
|
||||
client.client_description = values[index++];
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
logError(server_id, "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
|
||||
index - 1,
|
||||
ex.what(),
|
||||
offset.has_value() ? std::to_string(*offset) : "not given",
|
||||
limit.has_value() ? std::to_string(*limit) : "not given",
|
||||
set_index - 1
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(user_argument, client);
|
||||
});
|
||||
}
|
||||
+101
-100
@@ -8,126 +8,127 @@
|
||||
#include <Properties.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class VirtualServer;
|
||||
class DataClient;
|
||||
namespace ts::server {
|
||||
class VirtualServer;
|
||||
class DataClient;
|
||||
|
||||
struct ClientDatabaseInfo {
|
||||
ServerId sid;
|
||||
ClientDbId cldbid;
|
||||
std::chrono::time_point<std::chrono::system_clock> created;
|
||||
std::chrono::time_point<std::chrono::system_clock> lastjoin;
|
||||
std::string uniqueId;
|
||||
std::string lastName;
|
||||
uint32_t connections;
|
||||
};
|
||||
struct ClientDatabaseInfo {
|
||||
ServerId server_id;
|
||||
|
||||
struct CachedPermissionManager {
|
||||
ServerId sid;
|
||||
ClientDbId cldbid;
|
||||
std::weak_ptr<permission::v2::PermissionManager> manager;
|
||||
std::shared_ptr<permission::v2::PermissionManager> ownLock;
|
||||
std::chrono::time_point<std::chrono::system_clock> lastAccess;
|
||||
};
|
||||
ClientDbId client_database_id;
|
||||
std::string client_unique_id;
|
||||
std::string client_nickname;
|
||||
|
||||
struct CachedProperties {
|
||||
ServerId sid;
|
||||
ClientDbId cldbid;
|
||||
std::weak_ptr<Properties> properties;
|
||||
std::shared_ptr<Properties> ownLock;
|
||||
std::chrono::time_point<std::chrono::system_clock> lastAccess;
|
||||
};
|
||||
std::chrono::time_point<std::chrono::system_clock> client_created;
|
||||
std::chrono::time_point<std::chrono::system_clock> client_last_connected;
|
||||
|
||||
/*
|
||||
CREATE_TABLE("properties", "`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT", command_append_utf8);
|
||||
CREATE_TABLE("permissions", "`serverId` INT NOT NULL, `type` INT, `id` INT, `channelId` INT, `permId` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` INT, `grant` INT", command_append_utf8);
|
||||
*/
|
||||
struct StartupPermissionEntry {
|
||||
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
|
||||
uint64_t id = 0;
|
||||
ChannelId channelId = 0;
|
||||
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
|
||||
permission::PermissionValue value = 0;
|
||||
permission::PermissionValue grant = 0;
|
||||
uint32_t client_total_connections;
|
||||
};
|
||||
|
||||
bool flag_skip = false;
|
||||
bool flag_negate = false;
|
||||
};
|
||||
struct StartupPermissionEntry {
|
||||
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
|
||||
uint64_t id = 0;
|
||||
ChannelId channelId = 0;
|
||||
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
|
||||
permission::PermissionValue value = 0;
|
||||
permission::PermissionValue grant = 0;
|
||||
|
||||
struct StartupPropertyEntry {
|
||||
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
|
||||
uint64_t id{0};
|
||||
const property::PropertyDescription* info{&property::undefined_property_description};
|
||||
std::string value;
|
||||
};
|
||||
bool flag_skip = false;
|
||||
bool flag_negate = false;
|
||||
};
|
||||
|
||||
struct StartupCacheEntry {
|
||||
ServerId sid;
|
||||
struct StartupPropertyEntry {
|
||||
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
|
||||
uint64_t id{0};
|
||||
const property::PropertyDescription* info{&property::undefined_property_description};
|
||||
std::string value;
|
||||
};
|
||||
|
||||
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions;
|
||||
std::deque<std::unique_ptr<StartupPropertyEntry>> properties;
|
||||
};
|
||||
struct DatabaseClient {
|
||||
ClientDbId client_database_id;
|
||||
std::string client_unique_id;
|
||||
|
||||
struct FastPropertyEntry {
|
||||
const property::PropertyDescription* type;
|
||||
std::string value;
|
||||
};
|
||||
std::string client_nickname;
|
||||
std::string client_ip;
|
||||
|
||||
class DatabaseHelper {
|
||||
public:
|
||||
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
|
||||
static bool assignDatabaseId(sql::SqlManager *, ServerId id, std::shared_ptr<DataClient>);
|
||||
static std::mutex database_id_mutex;
|
||||
std::string client_created; /* seconds since epoch */
|
||||
std::string client_last_connected; /* seconds since epoch */
|
||||
std::string client_total_connections;
|
||||
|
||||
explicit DatabaseHelper(sql::SqlManager*);
|
||||
~DatabaseHelper();
|
||||
std::string client_login_name;
|
||||
std::string client_description; /* optional and only given sometimes */
|
||||
};
|
||||
|
||||
void loadStartupCache();
|
||||
size_t cacheBinarySize();
|
||||
void clearStartupCache(ServerId sid = 0);
|
||||
struct FastPropertyEntry {
|
||||
const property::PropertyDescription* type;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
|
||||
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
|
||||
struct CachedPermissionManager;
|
||||
struct StartupCacheEntry;
|
||||
class DatabaseHelper {
|
||||
public:
|
||||
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
|
||||
static bool assignDatabaseId(sql::SqlManager *, ServerId serverId, std::shared_ptr<DataClient>);
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
explicit DatabaseHelper(sql::SqlManager*);
|
||||
~DatabaseHelper();
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
void loadStartupCache();
|
||||
size_t cacheBinarySize();
|
||||
void clearStartupCache(ServerId sid = 0);
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
|
||||
void saveGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
void handleServerDelete(ServerId /* server id */);
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
void listDatabaseClients(
|
||||
ServerId /* server id */,
|
||||
const std::optional<int64_t>& offset,
|
||||
const std::optional<int64_t>& limit,
|
||||
void(* /* callback */)(void* /* user argument */, const DatabaseClient& /* client */),
|
||||
void* /* user argument */);
|
||||
|
||||
std::shared_ptr<Properties> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
|
||||
std::shared_ptr<Properties> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
|
||||
std::shared_ptr<Properties> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
|
||||
std::shared_ptr<Properties> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
|
||||
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
|
||||
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
|
||||
|
||||
bool deleteGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
|
||||
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
void tick();
|
||||
private:
|
||||
void loadStartupPermissionCache();
|
||||
void loadStartupPropertyCache();
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
bool use_startup_cache = false;
|
||||
threads::Mutex startup_lock;
|
||||
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
|
||||
void saveGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
sql::SqlManager* sql = nullptr;
|
||||
threads::Mutex permManagerLock;
|
||||
std::deque<CachedPermissionManager*> cachedPermissionManagers;
|
||||
threads::Mutex propsLock;
|
||||
std::deque<CachedProperties*> cachedProperties;
|
||||
};
|
||||
}
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
std::shared_ptr<Properties> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
|
||||
std::shared_ptr<Properties> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
|
||||
std::shared_ptr<Properties> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
|
||||
std::shared_ptr<Properties> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
|
||||
|
||||
void deleteGroupArtifacts(ServerId, GroupId);
|
||||
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
|
||||
|
||||
void tick();
|
||||
private:
|
||||
void loadStartupPermissionCache();
|
||||
void loadStartupPropertyCache();
|
||||
|
||||
bool use_startup_cache = false;
|
||||
threads::Mutex startup_lock;
|
||||
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
|
||||
|
||||
sql::SqlManager* sql = nullptr;
|
||||
|
||||
threads::Mutex cached_permission_manager_lock;
|
||||
std::deque<std::unique_ptr<CachedPermissionManager>> cached_permission_managers;
|
||||
|
||||
/* Attention: cached_permission_manager_lock should be locked! */
|
||||
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionManager> find_cached_permission_manager(ServerId /* server id */, ClientDbId /* client id */);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
//
|
||||
// Created by WolverinDEV on 12/05/2020.
|
||||
//
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <files/Config.h>
|
||||
|
||||
#include "./client/ConnectedClient.h"
|
||||
#include "FileServerHandler.h"
|
||||
|
||||
using namespace ts::server;
|
||||
using namespace ts::server::file;
|
||||
|
||||
FileServerHandler::FileServerHandler(ts::server::InstanceHandler *instance) : instance_{instance} {}
|
||||
|
||||
bool FileServerHandler::initialize(std::string &error) {
|
||||
if(!file::initialize(error, serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].as<std::string>(), serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as<uint16_t>()))
|
||||
return false;
|
||||
|
||||
|
||||
file::config::ssl_option_supplier = [&]{
|
||||
return this->instance_->sslManager()->web_ssl_options();
|
||||
};
|
||||
|
||||
auto server = file::server();
|
||||
assert(server);
|
||||
|
||||
auto& transfer = server->file_transfer();
|
||||
transfer.callback_transfer_registered = std::bind(&FileServerHandler::callback_transfer_registered, this, std::placeholders::_1);
|
||||
transfer.callback_transfer_started = std::bind(&FileServerHandler::callback_transfer_started, this, std::placeholders::_1);
|
||||
transfer.callback_transfer_finished = std::bind(&FileServerHandler::callback_transfer_finished, this, std::placeholders::_1);
|
||||
|
||||
transfer.callback_transfer_aborted = std::bind(&FileServerHandler::callback_transfer_aborted, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
transfer.callback_transfer_statistics = std::bind(&FileServerHandler::callback_transfer_statistics, this, std::placeholders::_1, std::placeholders::_2);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileServerHandler::finalize() {
|
||||
file::finalize();
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_registered(const std::shared_ptr<transfer::Transfer> &transfer) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) return; /* well that's bad */
|
||||
|
||||
const auto bytes = transfer->expected_file_size - transfer->file_offset;
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED] += bytes;
|
||||
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED] += bytes;
|
||||
} else {
|
||||
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED] += bytes;
|
||||
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED] += bytes;
|
||||
}
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(client && client->getUid() == transfer->client_unique_id) {
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
client->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED] += bytes;
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_UPLOADED] += bytes;
|
||||
} else {
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED] += bytes;
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED] += bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_aborted(const std::shared_ptr<transfer::Transfer> &transfer,
|
||||
const transfer::TransferStatistics &statistics,
|
||||
const ts::server::file::transfer::TransferError &error) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) return; /* well that's bad */
|
||||
|
||||
if(statistics.file_total_size < statistics.file_current_offset)
|
||||
return;
|
||||
|
||||
const int64_t bytes_left = statistics.file_total_size - statistics.file_current_offset;
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED] += -bytes_left;
|
||||
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED] += -bytes_left;
|
||||
} else {
|
||||
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED] += -bytes_left;
|
||||
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED] += -bytes_left;
|
||||
}
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(client && client->getUid() == transfer->client_unique_id) {
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
client->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED] += -bytes_left;
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_UPLOADED] += -bytes_left;
|
||||
} else {
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED] += -bytes_left;
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED] += -bytes_left;
|
||||
}
|
||||
|
||||
ts::command_builder notify{"notifystatusfiletransfer"};
|
||||
|
||||
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
|
||||
notify.put(0, "size", 0);
|
||||
|
||||
ts::command_result status{};
|
||||
using ErrorType = ts::server::file::transfer::TransferError::Type;
|
||||
switch (error.error_type) {
|
||||
case ErrorType::TRANSFER_TIMEOUT:
|
||||
status.reset(ts::command_result{error::file_transfer_connection_timeout});
|
||||
break;
|
||||
|
||||
case ErrorType::DISK_IO_ERROR:
|
||||
case ErrorType::DISK_TIMEOUT:
|
||||
case ErrorType::DISK_INITIALIZE_ERROR:
|
||||
status.reset(ts::command_result{error::file_io_error});
|
||||
break;
|
||||
|
||||
case ErrorType::UNKNOWN:
|
||||
case ErrorType::NETWORK_IO_ERROR:
|
||||
status.reset(ts::command_result{error::file_connection_lost});
|
||||
break;
|
||||
|
||||
case ErrorType::UNEXPECTED_CLIENT_DISCONNECT:
|
||||
case ErrorType::UNEXPECTED_DISK_EOF:
|
||||
status.reset(ts::command_result{error::file_transfer_interrupted});
|
||||
|
||||
case ErrorType::USER_REQUEST:
|
||||
status.reset(ts::command_result{error::file_transfer_canceled});
|
||||
break;
|
||||
}
|
||||
client->writeCommandResult(notify, status, "status");
|
||||
client->sendCommand(notify);
|
||||
}
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_statistics(const std::shared_ptr<transfer::Transfer> &transfer,
|
||||
const ts::server::file::transfer::TransferStatistics &statistics) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) return; /* well that's bad */
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(!client || client->getUid() != transfer->client_unique_id) {
|
||||
/* client not online anymore, but we could still log this as server traffic */
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
server->getServerStatistics()->logFileTransferIn(statistics.delta_file_bytes_transferred);
|
||||
} else {
|
||||
server->getServerStatistics()->logFileTransferOut(statistics.delta_file_bytes_transferred);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
client->getConnectionStatistics()->logFileTransferIn(statistics.delta_file_bytes_transferred);
|
||||
} else {
|
||||
client->getConnectionStatistics()->logFileTransferOut(statistics.delta_file_bytes_transferred);
|
||||
}
|
||||
|
||||
if(client->getType() == ClientType::CLIENT_TEAMSPEAK)
|
||||
return; /* TS3 does not know this notify */
|
||||
|
||||
ts::command_builder notify{"notifyfiletransferprogress"};
|
||||
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
|
||||
|
||||
notify.put_unchecked(0, "file_bytes_transferred", statistics.file_bytes_transferred);
|
||||
notify.put_unchecked(0, "network_bytes_send", statistics.network_bytes_send);
|
||||
notify.put_unchecked(0, "network_bytes_received", statistics.network_bytes_received);
|
||||
|
||||
notify.put_unchecked(0, "file_start_offset", statistics.file_start_offset);
|
||||
notify.put_unchecked(0, "file_current_offset", statistics.file_current_offset);
|
||||
notify.put_unchecked(0, "file_total_size", statistics.file_total_size);
|
||||
|
||||
notify.put_unchecked(0, "network_current_speed", statistics.current_speed);
|
||||
notify.put_unchecked(0, "network_average_speed", statistics.average_speed);
|
||||
|
||||
client->sendCommand(notify);
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_started(const std::shared_ptr<transfer::Transfer> &transfer) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) return; /* well that's bad */
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(!client || client->getUid() != transfer->client_unique_id) return;
|
||||
|
||||
|
||||
ts::command_builder notify{"notifyfiletransferstarted"};
|
||||
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
|
||||
client->sendCommand(notify);
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_finished(const std::shared_ptr<transfer::Transfer> &transfer) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) return; /* well that's bad */
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(!client || client->getUid() != transfer->client_unique_id) return;
|
||||
|
||||
|
||||
if(client->getType() == ClientType::CLIENT_TEAMSPEAK)
|
||||
return;
|
||||
|
||||
ts::command_builder notify{"notifystatusfiletransfer"};
|
||||
|
||||
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
|
||||
notify.put(0, "size", transfer->expected_file_size); /* not sure where TeamSpeak counts from */
|
||||
notify.put_unchecked(0, "status", (int) error::file_transfer_complete);
|
||||
notify.put_unchecked(0, "msg", findError(error::file_transfer_complete).message);
|
||||
|
||||
/* TODO: Some stats? */
|
||||
|
||||
client->sendCommand(notify);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <files/FileServer.h>
|
||||
|
||||
#include "./InstanceHandler.h"
|
||||
|
||||
namespace ts::server::file {
|
||||
class FileServerHandler {
|
||||
public:
|
||||
explicit FileServerHandler(InstanceHandler*);
|
||||
|
||||
bool initialize(std::string& /* error */);
|
||||
void finalize();
|
||||
private:
|
||||
InstanceHandler* instance_;
|
||||
|
||||
void callback_transfer_registered(const std::shared_ptr<transfer::Transfer>&);
|
||||
void callback_transfer_started(const std::shared_ptr<transfer::Transfer>&);
|
||||
void callback_transfer_finished(const std::shared_ptr<transfer::Transfer>&);
|
||||
|
||||
void callback_transfer_aborted(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferStatistics&, const transfer::TransferError&);
|
||||
void callback_transfer_statistics(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferStatistics&);
|
||||
};
|
||||
}
|
||||
+59
-31
@@ -6,7 +6,6 @@
|
||||
#include "VirtualServer.h"
|
||||
#include "src/client/ConnectedClient.h"
|
||||
#include "InstanceHandler.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -230,11 +229,13 @@ int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
|
||||
debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name());
|
||||
this->groups.push_back(group);
|
||||
|
||||
#if 0
|
||||
auto iconId = (IconId) group->icon_id();
|
||||
if(iconId != 0 && serverInstance && serverInstance->getFileServer() && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
|
||||
logMessage(this->getServerId(), "[FILE] Missing group icon (" + to_string(iconId) + ").");
|
||||
if(config::server::delete_missing_icon_permissions) group->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -450,7 +451,10 @@ bool GroupManager::deleteGroup(std::shared_ptr<Group> group) {
|
||||
LOG_SQL_CMD(res);
|
||||
flag_sql |= !res;
|
||||
|
||||
flag_sql &= serverInstance->databaseHelper()->deleteGroupPermissions(this->server.lock(), group->groupId());
|
||||
serverInstance->databaseHelper()->deleteGroupArtifacts(this->getServerId(), group->groupId());
|
||||
if(auto server{this->server.lock()}; server)
|
||||
server->getTokenManager()->handleGroupDelete(group->groupId());
|
||||
|
||||
if(flag_sql)
|
||||
logError(this->getServerId(), "Could not delete group {} ({}) from database. May leader to invalid data", group->name(), group->groupId());
|
||||
|
||||
@@ -617,41 +621,65 @@ bool GroupManager::isClientCached(const ClientDbId& client_database_id) {
|
||||
return this->resolve_cached_client(client_database_id) == nullptr;
|
||||
}
|
||||
|
||||
constexpr static auto kGroupMemberListQuery{R"(
|
||||
SELECT
|
||||
assignedGroups.cldbid,
|
||||
clients_server.client_unique_id,
|
||||
clients_server.client_nickname,
|
||||
assignedGroups.channelId,
|
||||
assignedGroups.until
|
||||
FROM assignedGroups
|
||||
INNER JOIN clients_server ON
|
||||
clients_server.client_database_id = assignedGroups.cldbid AND clients_server.server_id = :sid
|
||||
WHERE assignedGroups.`serverId` = :sid AND `groupId` = :gid;
|
||||
)"};
|
||||
|
||||
typedef std::vector<std::shared_ptr<GroupMember>> ResList;
|
||||
std::vector<std::shared_ptr<GroupMember>> GroupManager::listGroupMembers(std::shared_ptr<Group> group, bool names) { //TODO juse inner join only on names = true
|
||||
std::deque<GroupMember> GroupManager::listGroupMembers(std::shared_ptr<Group> group, bool names) {
|
||||
if(!isLocalGroup(group)){
|
||||
if(this->root) return this->root->listGroupMembers(group, names);
|
||||
if(this->root)
|
||||
return this->root->listGroupMembers(group, names);
|
||||
return {};
|
||||
}
|
||||
ResList result;
|
||||
|
||||
sql::command(this->sql,
|
||||
"SELECT assignedGroups.cldbid, assignedGroups.channelId, assignedGroups.until, clients.clientUid, clients.lastName FROM assignedGroups INNER JOIN clients ON (clients.cldbid = assignedGroups.cldbid AND clients.serverId = assignedGroups.serverId) WHERE assignedGroups.`serverId` = :sid AND `groupId` = :gid;",
|
||||
variable{":sid", this->getServerId()}, variable{":gid", group->groupId()})
|
||||
.query([&](ResList* list, int columnCount, char** values, char** columnName){
|
||||
std::shared_ptr<GroupMember> member = std::make_shared<GroupMember>();
|
||||
member->displayName = "undefined";
|
||||
member->uid = "undefined";
|
||||
for(int index = 0; index < columnCount; index++){
|
||||
if(values[index] == nullptr) {
|
||||
logError(this->getServerId(), string() + "Invalid value at " + columnName[index]);
|
||||
continue;
|
||||
}
|
||||
if(strcmp(columnName[index], "cldbid") == 0)
|
||||
member->cldbId = stoll(values[index]);
|
||||
else if(strcmp(columnName[index], "until") == 0)
|
||||
member->until = time_point<system_clock>() + milliseconds(stoll(values[index]));
|
||||
else if(strcmp(columnName[index], "clientUid") == 0)
|
||||
member->uid = values[index];
|
||||
else if(strcmp(columnName[index], "lastName") == 0)
|
||||
member->displayName = values[index];
|
||||
else if(strcmp(columnName[index], "channelId") == 0)
|
||||
member->channelId = stoll(values[index]);
|
||||
else cerr << "Invalid column name " << columnName[index] << endl;
|
||||
std::deque<GroupMember> result{};
|
||||
|
||||
size_t set_index{0};
|
||||
sql::command{this->sql, std::string{kGroupMemberListQuery}, variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}}
|
||||
.query([&](int length, std::string* values, std::string* names) {
|
||||
set_index++;
|
||||
|
||||
auto index{0};
|
||||
try {
|
||||
auto& member = result.emplace_back();
|
||||
assert(names[index] == "cldbid");
|
||||
member.cldbId = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_unique_id");
|
||||
member.uid = values[index++];
|
||||
|
||||
assert(names[index] == "client_nickname");
|
||||
member.displayName = values[index++];
|
||||
|
||||
assert(names[index] == "channelId");
|
||||
member.channelId = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "until");
|
||||
member.until = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoll(values[index++])};
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
result.pop_back();
|
||||
logError(this->getServerId(), "Failed to parse client group assignment for group {}: {}. Set index: {}, Column: {}",
|
||||
group->groupId(),
|
||||
ex.what(),
|
||||
set_index - 1,
|
||||
index - 1
|
||||
);
|
||||
return;
|
||||
}
|
||||
list->push_back(member);
|
||||
return 0;
|
||||
}, &result);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -198,7 +198,7 @@ namespace ts {
|
||||
bool renameGroup(std::shared_ptr<Group>, std::string);
|
||||
bool deleteGroup(std::shared_ptr<Group>);
|
||||
bool deleteAllGroups();
|
||||
std::vector<std::shared_ptr<GroupMember>> listGroupMembers(std::shared_ptr<Group>, bool names = false);
|
||||
std::deque<GroupMember> listGroupMembers(std::shared_ptr<Group>, bool names = false);
|
||||
std::vector<std::shared_ptr<GroupAssignment>> listGroupAssignments(ClientDbId client);
|
||||
|
||||
void cleanupAssignments(ClientDbId);
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
#define XFREE undefined_free
|
||||
#define XREALLOC undefined_realloc
|
||||
|
||||
#include <netdb.h>
|
||||
#include "src/weblist/WebListManager.h"
|
||||
#include <log/LogUtils.h>
|
||||
#include "InstanceHandler.h"
|
||||
#include "src/client/InternalClient.h"
|
||||
#include "src/server/QueryServer.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "SignalHandler.h"
|
||||
#include "src/manager/PermissionNameMapper.h"
|
||||
#include "./FileServerHandler.h"
|
||||
#include <ThreadPool/Timer.h>
|
||||
#include "ShutdownHelper.h"
|
||||
#include <sys/utsname.h>
|
||||
@@ -21,13 +19,15 @@
|
||||
#include <misc/hex.h>
|
||||
#include <misc/rnd.h>
|
||||
#include <misc/strobf.h>
|
||||
#include <jemalloc/jemalloc.h>
|
||||
#include <protocol/buffers.h>
|
||||
|
||||
#ifndef _POSIX_SOURCE
|
||||
#define _POSIX_SOURCE
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <files/FileServer.h>
|
||||
#include "./manager/ActionLogger.h"
|
||||
|
||||
#undef _POSIX_SOURCE
|
||||
|
||||
using namespace std;
|
||||
@@ -50,6 +50,13 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
}
|
||||
this->dbHelper = new DatabaseHelper(this->getSql());
|
||||
|
||||
this->action_logger_ = std::make_unique<log::ActionLogger>();
|
||||
if(!this->action_logger_->initialize(error_message)) {
|
||||
logCritical(LOG_INSTANCE, "Failed to initialize instance action logs: {}", error_message);
|
||||
logCritical(LOG_INSTANCE, "Action log has been disabled.");
|
||||
this->action_logger_->finalize();
|
||||
}
|
||||
|
||||
this->_properties = new Properties();
|
||||
this->_properties->register_property_type<property::InstanceProperties>();
|
||||
this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT] = ts::config::binding::DefaultFilePort;
|
||||
@@ -281,31 +288,11 @@ bool InstanceHandler::startInstance() {
|
||||
}
|
||||
|
||||
this->loadWebCertificate();
|
||||
fileServer = new ts::server::LocalFileServer();
|
||||
{
|
||||
auto bindings_string = this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].as<string>();
|
||||
auto port = this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as<uint16_t>();
|
||||
auto ft_bindings = net::resolve_bindings(bindings_string, port);
|
||||
deque<shared_ptr<LocalFileServer::Binding>> bindings;
|
||||
|
||||
for(auto& binding : ft_bindings) {
|
||||
if(!get<2>(binding).empty()) {
|
||||
logError(LOG_FT, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
|
||||
continue;
|
||||
}
|
||||
auto entry = make_shared<LocalFileServer::Binding>();
|
||||
memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage));
|
||||
|
||||
entry->file_descriptor = -1;
|
||||
entry->event_accept = nullptr;
|
||||
bindings.push_back(entry);
|
||||
}
|
||||
|
||||
logMessage(LOG_FT, "Starting server on {}:{}", bindings_string, port);
|
||||
if(!fileServer->start(bindings, errorMessage)) {
|
||||
logCritical(LOG_FT, "Failed to start server: {}", errorMessage);
|
||||
return false;
|
||||
}
|
||||
this->file_server_handler_ = new file::FileServerHandler{this};
|
||||
if(!this->file_server_handler_->initialize(errorMessage)) {
|
||||
logCritical(LOG_FT, "Failed to initialize server: {}", errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(config::query::sslMode > 0) {
|
||||
@@ -443,14 +430,17 @@ void InstanceHandler::stopInstance() {
|
||||
debugMessage(LOG_QUERY, "Query server stopped");
|
||||
|
||||
debugMessage(LOG_FT, "Stopping file server");
|
||||
if (this->fileServer) this->fileServer->stop();
|
||||
delete this->fileServer;
|
||||
this->fileServer = nullptr;
|
||||
file::finalize();
|
||||
debugMessage(LOG_FT, "File server stopped");
|
||||
|
||||
this->save_channel_permissions();
|
||||
this->save_group_permissions();
|
||||
|
||||
if(this->file_server_handler_) {
|
||||
this->file_server_handler_->finalize();
|
||||
delete std::exchange(this->file_server_handler_, nullptr);
|
||||
}
|
||||
|
||||
delete this->sslMgr;
|
||||
this->sslMgr = nullptr;
|
||||
|
||||
@@ -519,10 +509,6 @@ void InstanceHandler::tickInstance() {
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> fileserver tick", milliseconds(5));
|
||||
if(this->fileServer) this->fileServer->instanceTick();
|
||||
}
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> sql_test tick", milliseconds(5));
|
||||
if(this->sql && this->active) {
|
||||
|
||||
@@ -23,6 +23,14 @@ namespace ts {
|
||||
class LicenseService;
|
||||
}
|
||||
|
||||
namespace file {
|
||||
class FileServerHandler;
|
||||
}
|
||||
|
||||
namespace log {
|
||||
class ActionLogger;
|
||||
}
|
||||
|
||||
class InstanceHandler {
|
||||
public:
|
||||
explicit InstanceHandler(SqlDataManager*);
|
||||
@@ -42,12 +50,13 @@ namespace ts {
|
||||
std::shared_mutex& getChannelTreeLock() { return this->default_tree_lock; }
|
||||
|
||||
VirtualServerManager* getVoiceServerManager(){ return this->voiceServerManager; }
|
||||
LocalFileServer* getFileServer(){ return fileServer; }
|
||||
QueryServer* getQueryServer(){ return queryServer; }
|
||||
DatabaseHelper* databaseHelper(){ return this->dbHelper; }
|
||||
BanManager* banManager(){ return this->banMgr; }
|
||||
ssl::SSLManager* sslManager(){ return this->sslMgr; }
|
||||
sql::SqlManager* getSql(){ return sql->sql(); }
|
||||
log::ActionLogger* action_logger() { return &*this->action_logger_; }
|
||||
file::FileServerHandler* getFileServerHandler() { return this->file_server_handler_; }
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> getStartTimestamp(){ return startTimestamp; }
|
||||
|
||||
@@ -110,12 +119,13 @@ namespace ts {
|
||||
std::chrono::system_clock::time_point memcleanTimestamp;
|
||||
SqlDataManager* sql;
|
||||
|
||||
LocalFileServer* fileServer = nullptr;
|
||||
QueryServer* queryServer = nullptr;
|
||||
VirtualServerManager* voiceServerManager = nullptr;
|
||||
DatabaseHelper* dbHelper = nullptr;
|
||||
BanManager* banMgr = nullptr;
|
||||
ssl::SSLManager* sslMgr = nullptr;
|
||||
file::FileServerHandler* file_server_handler_{nullptr};
|
||||
std::unique_ptr<log::ActionLogger> action_logger_{nullptr};
|
||||
|
||||
ts::Properties* _properties = nullptr;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "InstanceHandler.h"
|
||||
#include "src/client/InternalClient.h"
|
||||
#include "src/server/QueryServer.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
@@ -163,6 +163,7 @@ struct SnapshotPermissionEntry {
|
||||
}
|
||||
};
|
||||
|
||||
#if 0
|
||||
std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host,
|
||||
uint16_t port, const ts::Command &arguments,
|
||||
std::string &error) {
|
||||
@@ -692,6 +693,7 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
|
||||
server->ensureValidDefaultGroups();
|
||||
return server;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct CommandTuple {
|
||||
Command& cmd;
|
||||
@@ -771,8 +773,8 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
|
||||
int index = 0;
|
||||
|
||||
if(version == -1) version = 2; //Auto versioned
|
||||
if(version < 0 || version > 2) {
|
||||
if(version == -1) version = 3; //Auto versioned
|
||||
if(version < 0 || version > 3) {
|
||||
error = "Invalid snapshot version!";
|
||||
return false;
|
||||
}
|
||||
@@ -817,44 +819,26 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
//Clients
|
||||
{
|
||||
cmd[index]["begin_clients"] = "";
|
||||
CommandTuple parm{cmd, index, version};
|
||||
auto res = sql::command(server->getSql(), "SELECT `cldbid`, `clientUid`, `firstConnect`, `lastName` FROM `clients` WHERE `serverId` = :sid",
|
||||
variable{":sid", server->getServerId()}
|
||||
).query([&](CommandTuple* commandIndex, int length, char** value, char** name) {
|
||||
ClientDbId cldbid = 0;
|
||||
int64_t clientCreated = 0;
|
||||
string clientUid;
|
||||
string lastName;
|
||||
string description; //TODO description
|
||||
|
||||
for(int idx = 0; idx < length; idx++) {
|
||||
try {
|
||||
if(strcmp(name[idx], "cldbid") == 0)
|
||||
cldbid = value[idx] && strlen(value[idx]) > 0 ? stoul(value[idx]) : 0;
|
||||
else if(strcmp(name[idx], "clientUid") == 0)
|
||||
clientUid = value[idx];
|
||||
else if(strcmp(name[idx], "firstConnect") == 0)
|
||||
clientCreated = value[idx] && strlen(value[idx]) > 0 ? stoll(value[idx]) : 0L;
|
||||
else if(strcmp(name[idx], "lastName") == 0)
|
||||
lastName = value[idx];
|
||||
} catch (std::exception& ex) {
|
||||
logError(0, "Failed to write snapshot client (Skipping it)! Message: {} @ {} => {}", ex.what(), name[idx], value[idx]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
struct CallbackArgument {
|
||||
Command& command;
|
||||
int& index;
|
||||
int version;
|
||||
};
|
||||
|
||||
CallbackArgument callback_argument{cmd, index, version};
|
||||
this->handle->databaseHelper()->listDatabaseClients(server->getServerId(), std::nullopt, std::nullopt, [](void* ptr_argument, const DatabaseClient& client) {
|
||||
auto argument = (CallbackArgument*) ptr_argument;
|
||||
|
||||
commandIndex->cmd[commandIndex->index]["client_id"] = cldbid;
|
||||
commandIndex->cmd[commandIndex->index]["client_unique_id"] = clientUid;
|
||||
commandIndex->cmd[commandIndex->index]["client_nickname"] = lastName;
|
||||
commandIndex->cmd[commandIndex->index]["client_created"] = clientCreated;
|
||||
commandIndex->cmd[commandIndex->index]["client_description"] = description;
|
||||
if(commandIndex->version == 0)
|
||||
commandIndex->cmd[commandIndex->index]["client_unread_messages"] = 0;
|
||||
commandIndex->index++;
|
||||
return 0;
|
||||
}, &parm);
|
||||
LOG_SQL_CMD(res);
|
||||
argument->command[argument->index]["client_id"] = client.client_database_id;
|
||||
argument->command[argument->index]["client_unique_id"] = client.client_unique_id;
|
||||
argument->command[argument->index]["client_nickname"] = client.client_nickname;
|
||||
argument->command[argument->index]["client_created"] = client.client_created;
|
||||
argument->command[argument->index]["client_description"] = client.client_description;
|
||||
if(argument->version == 0)
|
||||
argument->command[argument->index]["client_unread_messages"] = 0;
|
||||
argument->index++;
|
||||
}, &callback_argument);
|
||||
cmd[index++]["end_clients"] = "";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <log/LogUtils.h>
|
||||
#include <StringVariable.h>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include <src/terminal/PipedTerminal.h>
|
||||
#include "ShutdownHelper.h"
|
||||
#include "InstanceHandler.h"
|
||||
|
||||
@@ -21,6 +22,7 @@ void ts::server::shutdownInstance(const std::string& message) {
|
||||
logCriticalFmt(true, 0, "Could not shutdown server within 30 seconds! (Hangup!)");
|
||||
logCriticalFmt(true, 0, "Killing server!");
|
||||
|
||||
terminal::finalize_pipe();
|
||||
auto force_kill = std::thread([]{
|
||||
threads::self::sleep_for(chrono::seconds(5));
|
||||
logCriticalFmt(true, 0, "Failed to exit normally!");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <csignal>
|
||||
#include <log/LogUtils.h>
|
||||
#include <experimental/filesystem>
|
||||
#include <src/terminal/PipedTerminal.h>
|
||||
|
||||
using namespace std;
|
||||
namespace fs = std::experimental::filesystem;
|
||||
@@ -44,6 +45,8 @@ static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
|
||||
logCritical(LOG_GENERAL, "Official issue and bug tracker url: https://github.com/TeaSpeak/TeaSpeak/issues");
|
||||
logCritical(LOG_GENERAL, "Any reports of crashes are useless if you not provide the above generated crashlog!");
|
||||
logCritical(LOG_GENERAL, "Stopping server");
|
||||
|
||||
terminal::finalize_pipe();
|
||||
ts::server::shutdownInstance(ts::config::messages::applicationCrashed);
|
||||
while(!mainThreadDone) threads::self::sleep_for(chrono::seconds(1));
|
||||
return succeeded;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <misc/timer.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/sassert.h>
|
||||
#include <src/manager/ActionLogger.h>
|
||||
#include "InstanceHandler.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -323,7 +324,7 @@ void VirtualServer::notify_client_kick(
|
||||
*
|
||||
* Note: channel cant be a ref because the channel itself gets deleted!
|
||||
*/
|
||||
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock) {
|
||||
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock, bool temp_delete) {
|
||||
if(!tree_lock.owns_lock())
|
||||
tree_lock.lock();
|
||||
if(channel->deleted)
|
||||
@@ -358,7 +359,12 @@ void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const
|
||||
tree_lock.lock(); /* no clients left within that tree */
|
||||
command_locks.clear();
|
||||
|
||||
auto channel_ids = this->channelTree->delete_channel_root(channel);
|
||||
auto deleted_channels = this->channelTree->delete_channel_root(channel);
|
||||
log::ChannelDeleteReason delete_reason{temp_delete ? log::ChannelDeleteReason::EMPTY : log::ChannelDeleteReason::USER_ACTION};
|
||||
for(const auto& deleted_channel : deleted_channels) {
|
||||
serverInstance->action_logger()->channel_logger.log_channel_delete(this->serverId, invoker, deleted_channel->channelId(), channel == deleted_channel ? delete_reason : log::ChannelDeleteReason::PARENT_DELETED);
|
||||
}
|
||||
|
||||
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
|
||||
unique_lock client_channel_lock(client->channel_lock);
|
||||
client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker);
|
||||
|
||||
@@ -187,7 +187,7 @@ void VirtualServer::executeServerTick() {
|
||||
if(system_clock::now() - last_left < deleteTimeout) continue; //One second stay
|
||||
if(system_clock::now() - channel_created < deleteTimeout + seconds(1)) continue; //One second stay
|
||||
|
||||
this->delete_channel(server_channel, this->serverRoot, "temporary autodelete", channel_lock);
|
||||
this->delete_channel(server_channel, this->serverRoot, "temporary auto delete", channel_lock, true);
|
||||
if(channel_lock.owns_lock())
|
||||
channel_lock.unlock();
|
||||
}
|
||||
@@ -209,20 +209,6 @@ void VirtualServer::executeServerTick() {
|
||||
BEGIN_TIMINGS();
|
||||
|
||||
this->serverStatistics->tick();
|
||||
|
||||
if(fileStatisticsTimestamp + seconds(5) < system_clock::now()) {
|
||||
fileStatisticsTimestamp = system_clock::now();
|
||||
auto update = this->serverStatistics->mark_file_bytes();
|
||||
if(update.first > 0) {
|
||||
this->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED] += update.first;
|
||||
this->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED] += update.first;
|
||||
}
|
||||
if(update.second > 0) {
|
||||
this->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED] += update.second;
|
||||
this->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED] += update.second;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(this->join_attempts_lock);
|
||||
if(system_clock::now() > this->join_last_decrease + seconds(5)) {
|
||||
|
||||
+116
-31
@@ -10,6 +10,8 @@
|
||||
#include <misc/digest.h>
|
||||
#include <misc/base64.h>
|
||||
|
||||
#include <files/FileServer.h>
|
||||
|
||||
#include "weblist/WebListManager.h"
|
||||
#include "./client/web/WebClient.h"
|
||||
#include "./client/voice/VoiceClient.h"
|
||||
@@ -18,13 +20,13 @@
|
||||
#include "./client/query/QueryClient.h"
|
||||
#include "music/MusicBotManager.h"
|
||||
#include "server/VoiceServer.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "server/QueryServer.h"
|
||||
#include "InstanceHandler.h"
|
||||
#include "Configuration.h"
|
||||
#include "VirtualServer.h"
|
||||
#include "src/manager/ConversationManager.h"
|
||||
#include <misc/sassert.h>
|
||||
#include <src/manager/ActionLogger.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -39,7 +41,6 @@ using namespace ts::buffer;
|
||||
#define BUILD_VERSION "Unknown build"
|
||||
#endif
|
||||
|
||||
extern ts::server::InstanceHandler* serverInstance;
|
||||
VirtualServer::VirtualServer(uint16_t serverId, sql::SqlManager* database) : serverId(serverId), sql(database) {
|
||||
memtrack::allocated<VirtualServer>(this);
|
||||
}
|
||||
@@ -52,10 +53,17 @@ bool VirtualServer::initialize(bool test_properties) {
|
||||
if(prop.type() == property::VIRTUALSERVER_DISABLE_IP_SAVING) {
|
||||
this->_disable_ip_saving = prop.as<bool>();
|
||||
return;
|
||||
}
|
||||
if(prop.type() == property::VIRTUALSERVER_CODEC_ENCRYPTION_MODE) {
|
||||
} else if(prop.type() == property::VIRTUALSERVER_CODEC_ENCRYPTION_MODE) {
|
||||
this->_voice_encryption_mode = prop.as<int>();
|
||||
return;
|
||||
} else if(prop.type() == property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH) {
|
||||
auto file_vs = file::server()->find_virtual_server(this->getServerId());
|
||||
if(!file_vs) return;
|
||||
file_vs->max_networking_upload_bandwidth(prop.as_save<int64_t>([]{ return -1; }));
|
||||
} else if(prop.type() == property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH) {
|
||||
auto file_vs = file::server()->find_virtual_server(this->getServerId());
|
||||
if(!file_vs) return;
|
||||
file_vs->max_networking_download_bandwidth(prop.as_save<int64_t>([]{ return -1; }));
|
||||
}
|
||||
std::string sql{};
|
||||
if(prop.type() == property::VIRTUALSERVER_HOST)
|
||||
@@ -72,6 +80,60 @@ bool VirtualServer::initialize(bool test_properties) {
|
||||
this->properties()[property::VIRTUALSERVER_ID] = serverId;
|
||||
this->_disable_ip_saving = this->properties()[property::VIRTUALSERVER_DISABLE_IP_SAVING];
|
||||
|
||||
/* initialize logging */
|
||||
{
|
||||
auto server_id = this->serverId;
|
||||
auto sync_property = [server_id](Property& prop) {
|
||||
log::LoggerGroup action_type;
|
||||
switch (prop.type().property_index) {
|
||||
case property::VIRTUALSERVER_LOG_SERVER:
|
||||
action_type = log::LoggerGroup::SERVER;
|
||||
break;
|
||||
|
||||
case property::VIRTUALSERVER_LOG_CHANNEL:
|
||||
action_type = log::LoggerGroup::CHANNEL;
|
||||
break;
|
||||
|
||||
case property::VIRTUALSERVER_LOG_CLIENT:
|
||||
action_type = log::LoggerGroup::CLIENT;
|
||||
break;
|
||||
|
||||
case property::VIRTUALSERVER_LOG_FILETRANSFER:
|
||||
action_type = log::LoggerGroup::FILES;
|
||||
break;
|
||||
|
||||
case property::VIRTUALSERVER_LOG_PERMISSIONS:
|
||||
action_type = log::LoggerGroup::PERMISSION;
|
||||
break;
|
||||
|
||||
case property::VIRTUALSERVER_LOG_QUERY:
|
||||
action_type = log::LoggerGroup::QUERY;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
serverInstance->action_logger()->toggle_logging_group(server_id, action_type, prop.as_save<bool>([]{ return true; }));
|
||||
};
|
||||
|
||||
for(const property::VirtualServerProperties& property : {
|
||||
property::VIRTUALSERVER_LOG_SERVER,
|
||||
property::VIRTUALSERVER_LOG_CHANNEL,
|
||||
property::VIRTUALSERVER_LOG_CLIENT,
|
||||
property::VIRTUALSERVER_LOG_FILETRANSFER,
|
||||
property::VIRTUALSERVER_LOG_QUERY,
|
||||
property::VIRTUALSERVER_LOG_PERMISSIONS
|
||||
}) {
|
||||
auto prop = this->_properties->find(property::PROP_TYPE_SERVER, property);
|
||||
sync_property(prop);
|
||||
}
|
||||
|
||||
this->_properties->registerNotifyHandler([sync_property](Property& prop){
|
||||
sync_property(prop);
|
||||
});
|
||||
}
|
||||
|
||||
if(!properties()[property::VIRTUALSERVER_KEYPAIR].as<std::string>().empty()){
|
||||
debugMessage(this->serverId, "Importing server keypair");
|
||||
this->_serverKey = new ecc_key;
|
||||
@@ -130,7 +192,7 @@ bool VirtualServer::initialize(bool test_properties) {
|
||||
}
|
||||
|
||||
channelTree->deleteSemiPermanentChannels();
|
||||
if(channelTree->channel_count() == 0){
|
||||
if(channelTree->channel_count() == 0) {
|
||||
logMessage(this->serverId, "Creating new channel tree (Copy from server 0)");
|
||||
LOG_SQL_CMD(sql::command(this->getSql(), "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) SELECT :serverId AS `serverId`, `channelId`, `type`, `parentId` FROM `channels` WHERE `serverId` = 0", variable{":serverId", this->serverId}).execute());
|
||||
LOG_SQL_CMD(sql::command(this->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :serverId AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `serverId` = 0 AND `type` = :type",
|
||||
@@ -196,8 +258,32 @@ bool VirtualServer::initialize(bool test_properties) {
|
||||
this->serverAdmin->server = nullptr;
|
||||
this->registerInternalClient(this->serverAdmin); /* lets assign server id 0 */
|
||||
|
||||
if(serverInstance->getFileServer())
|
||||
serverInstance->getFileServer()->setupServer(self.lock());
|
||||
{
|
||||
using ErrorType = file::filesystem::ServerCommandErrorType;
|
||||
|
||||
auto file_vs = file::server()->register_server(this->getServerId());
|
||||
auto initialize_result = file::server()->file_system().initialize_server(file_vs);
|
||||
if(!initialize_result->wait_for(std::chrono::seconds{5})) {
|
||||
logError(this->getServerId(), "Failed to wait for file directory initialisation.");
|
||||
} else if(!initialize_result->succeeded()) {
|
||||
switch (initialize_result->error().error_type) {
|
||||
case ErrorType::FAILED_TO_CREATE_DIRECTORIES:
|
||||
logError(this->getServerId(), "Failed to create server file directories ({}).", initialize_result->error().error_message);
|
||||
break;
|
||||
|
||||
case ErrorType::UNKNOWN:
|
||||
case ErrorType::FAILED_TO_DELETE_DIRECTORIES:
|
||||
logError(this->getServerId(), "Failed to initialize server file directory due to an unknown error: {}/{}",
|
||||
(int) initialize_result->error().error_type, initialize_result->error().error_message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this->properties()[property::VIRTUALSERVER_FILEBASE] = file::server()->file_base_path();
|
||||
file_vs->max_networking_download_bandwidth(this->properties()[property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH].as_save<int64_t>([]{ return -1; }));
|
||||
file_vs->max_networking_upload_bandwidth(this->properties()[property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH].as_save<int64_t>([]{ return -1; }));
|
||||
}
|
||||
|
||||
this->channelTree->printChannelTree([&](std::string msg){ debugMessage(this->serverId, msg); });
|
||||
this->musicManager = make_shared<music::MusicBotManager>(self.lock());
|
||||
@@ -205,11 +291,13 @@ bool VirtualServer::initialize(bool test_properties) {
|
||||
this->musicManager->load_playlists();
|
||||
this->musicManager->load_bots();
|
||||
|
||||
#if 0
|
||||
if(this->properties()[property::VIRTUALSERVER_ICON_ID] != (IconId) 0)
|
||||
if(!serverInstance->getFileServer()->iconExists(self.lock(), this->properties()[property::VIRTUALSERVER_ICON_ID])) {
|
||||
debugMessage(this->getServerId(), "Removing invalid icon id of server");
|
||||
this->properties()[property::VIRTUALSERVER_ICON_ID] = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for(const auto& type : vector<property::VirtualServerProperties>{
|
||||
property::VIRTUALSERVER_DOWNLOAD_QUOTA,
|
||||
@@ -227,9 +315,6 @@ bool VirtualServer::initialize(bool test_properties) {
|
||||
}
|
||||
}
|
||||
|
||||
if(this->properties()[property::VIRTUALSERVER_FILEBASE].value().empty())
|
||||
this->properties()[property::VIRTUALSERVER_FILEBASE] = serverInstance->getFileServer()->server_file_base(self.lock());
|
||||
|
||||
/* lets cleanup the conversations for not existent channels */
|
||||
this->_conversation_manager->synchronize_channels();
|
||||
return true;
|
||||
@@ -800,8 +885,6 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
|
||||
bool have_skip_permission = false;
|
||||
int skip_permission_type = -1; /* -1 := unset | 0 := skip, not explicit | 1 := skip, explicit */
|
||||
|
||||
bool have_skip;
|
||||
|
||||
/*
|
||||
* server_group_data[0] := Server group id
|
||||
* server_group_data[1] := Skip flag
|
||||
@@ -845,10 +928,10 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
|
||||
server_group_data_initialized = true;
|
||||
active_server_group = nullptr;
|
||||
|
||||
auto groups = cache->getGroupAssignments(this, client_dbid, client_type);
|
||||
server_group_data.resize(groups.size());
|
||||
auto assigned_groups = cache->getGroupAssignments(this, client_dbid, client_type);
|
||||
server_group_data.resize(assigned_groups.size());
|
||||
auto it = server_group_data.begin();
|
||||
for(auto& group : groups) {
|
||||
for(auto& group : assigned_groups) {
|
||||
auto group_permissions = group->group->permissions();
|
||||
auto permission_flags = group_permissions->permission_flags(permission_type);
|
||||
|
||||
@@ -898,13 +981,6 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
|
||||
};
|
||||
|
||||
for(const auto& permission : permissions) {
|
||||
if(permission == permission::b_client_skip_channelgroup_permissions) {
|
||||
if(skip_permission_type == -1) /* initialize skip flag */
|
||||
calculate_skip();
|
||||
result.push_back({permission, {have_skip_permission, skip_permission_type == 1}});
|
||||
continue;
|
||||
}
|
||||
|
||||
server_group_data_initialized = false; /* reset all group data */
|
||||
auto client_permission_flags = cache->client_permissions->permission_flags(permission);
|
||||
/* lets try to resolve the channel specific permission */
|
||||
@@ -918,27 +994,28 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
|
||||
}
|
||||
|
||||
|
||||
have_skip = channel_id == 0;
|
||||
if(!have_skip) {
|
||||
bool skip_channel_permissions = channel_id == 0;
|
||||
if(!skip_channel_permissions) {
|
||||
/* look if somewhere is the skip permission flag set */
|
||||
if(skip_permission_type == -1) /* initialize skip flag */
|
||||
calculate_skip();
|
||||
have_skip = have_skip_permission;
|
||||
skip_channel_permissions = have_skip_permission;
|
||||
}
|
||||
if(!have_skip) {
|
||||
if(!skip_channel_permissions) {
|
||||
/* okey we've no global skip. Then now lookup the groups and the client permissions */
|
||||
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
|
||||
/* okey the client has the permission, this counts */
|
||||
have_skip = client_permission_flags.skip;
|
||||
skip_channel_permissions = client_permission_flags.skip;
|
||||
} else {
|
||||
if(!server_group_data_initialized)
|
||||
initialize_group_data(permission);
|
||||
|
||||
if(active_server_group)
|
||||
have_skip = std::get<1>(*active_server_group);
|
||||
skip_channel_permissions = std::get<1>(*active_server_group);
|
||||
}
|
||||
}
|
||||
|
||||
if(!have_skip) {
|
||||
if(!skip_channel_permissions) {
|
||||
/* lookup the channel group */
|
||||
{
|
||||
auto channel_assignment = cache->getChannelAssignment(this, client_dbid, channel_id);
|
||||
@@ -1186,7 +1263,15 @@ void VirtualServer::send_text_message(const std::shared_ptr<BasicChannel> &chann
|
||||
auto channel_id = channel->channelId();
|
||||
auto now = chrono::system_clock::now();
|
||||
|
||||
bool conversation_private = channel->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as<bool>();
|
||||
bool conversation_private;
|
||||
auto conversation_mode = channel->properties()[property::CHANNEL_CONVERSATION_MODE].as<ChannelConversationMode>();
|
||||
if(conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE) {
|
||||
/* nothing to do */
|
||||
return;
|
||||
} else {
|
||||
conversation_private = conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE;
|
||||
}
|
||||
|
||||
auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>();
|
||||
for(const auto& client : this->getClients()) {
|
||||
if(client->connectionState() != ConnectionState::CONNECTED)
|
||||
|
||||
@@ -58,7 +58,6 @@ namespace ts {
|
||||
class InstanceHandler;
|
||||
class VoiceServer;
|
||||
class QueryServer;
|
||||
class LocalFileServer;
|
||||
class SpeakingClient;
|
||||
|
||||
class WebControlServer;
|
||||
@@ -186,6 +185,10 @@ namespace ts {
|
||||
inline ServerChannelTree* getChannelTree(){ return this->channelTree; }
|
||||
inline GroupManager* getGroupManager() { return this->groups; }
|
||||
|
||||
[[nodiscard]] inline auto getTokenManager() -> token::TokenManager* {
|
||||
return this->tokenManager;
|
||||
}
|
||||
|
||||
bool notifyServerEdited(std::shared_ptr<ConnectedClient>, std::deque<std::string> keys);
|
||||
bool notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient>, const std::deque<const property::PropertyDescription*>& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */
|
||||
inline bool notifyClientPropertyUpdates(const std::shared_ptr<ConnectedClient>& client, const std::deque<property::ClientProperties>& keys, bool selfNotify = true) {
|
||||
@@ -271,7 +274,8 @@ namespace ts {
|
||||
std::shared_ptr<ServerChannel> /* target channel */,
|
||||
const std::shared_ptr<ConnectedClient>& /* invoker */,
|
||||
const std::string& /* kick message */,
|
||||
std::unique_lock<std::shared_mutex>& /* tree lock */
|
||||
std::unique_lock<std::shared_mutex>& /* tree lock */,
|
||||
bool temporary_auto_delete
|
||||
);
|
||||
|
||||
void send_text_message(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* sender */, const std::string& /* message */);
|
||||
@@ -279,6 +283,7 @@ namespace ts {
|
||||
inline int voice_encryption_mode() { return this->_voice_encryption_mode; }
|
||||
inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->_conversation_manager; }
|
||||
|
||||
inline auto& get_channel_tree_lock() { return this->channel_tree_lock; }
|
||||
|
||||
void update_channel_from_permissions(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* issuer */);
|
||||
protected:
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
#include "src/server/VoiceServer.h"
|
||||
#include "src/client/query/QueryClient.h"
|
||||
#include "InstanceHandler.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "src/client/ConnectedClient.h"
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include <files/FileServer.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -110,6 +110,9 @@ bool VirtualServerManager::initialize(bool autostart) {
|
||||
if(id == 0) {
|
||||
logError(LOG_INSTANCE, "Failed to load virtual server from database. Server id is zero!");
|
||||
return 0;
|
||||
} else if(id == 0xFFFF) {
|
||||
/* snapshot server */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(host.empty()) {
|
||||
@@ -256,6 +259,7 @@ uint16_t VirtualServerManager::next_available_port(const std::string& host_strin
|
||||
|
||||
ts::ServerId VirtualServerManager::next_available_server_id(bool& success) {
|
||||
auto server_id_base = this->handle->properties()[property::SERVERINSTANCE_VIRTUAL_SERVER_ID_INDEX].as<ServerId>();
|
||||
/* ensure we're not using 0xFFFF (This is the snapshot server) */
|
||||
if(server_id_base > 65530) {
|
||||
success = false;
|
||||
return 0;
|
||||
@@ -340,7 +344,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
|
||||
if(!sid_success)
|
||||
return nullptr;
|
||||
|
||||
this->delete_server_in_db(serverId); /* just to ensure */
|
||||
this->delete_server_in_db(serverId, false); /* just to ensure */
|
||||
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
|
||||
auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;",
|
||||
@@ -418,8 +422,29 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
|
||||
}
|
||||
|
||||
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
|
||||
this->delete_server_in_db(server->serverId);
|
||||
this->handle->getFileServer()->deleteServer(server);
|
||||
this->delete_server_in_db(server->serverId, false);
|
||||
this->handle->databaseHelper()->handleServerDelete(server->serverId);
|
||||
|
||||
if(auto file_vs = file::server()->find_virtual_server(server->getServerId()); file_vs) {
|
||||
using ErrorType = file::filesystem::ServerCommandErrorType;
|
||||
|
||||
auto delete_result = file::server()->file_system().delete_server(file_vs);
|
||||
if(!delete_result->wait_for(std::chrono::seconds{5})) {
|
||||
logError(LOG_INSTANCE, "Failed to wait for file directory initialisation.");
|
||||
} else if(!delete_result->succeeded()) {
|
||||
switch (delete_result->error().error_type) {
|
||||
case ErrorType::FAILED_TO_DELETE_DIRECTORIES:
|
||||
logError(LOG_INSTANCE, "Failed to delete server {} file directories ({}).", server->getServerId(), delete_result->error().error_message);
|
||||
break;
|
||||
|
||||
case ErrorType::UNKNOWN:
|
||||
case ErrorType::FAILED_TO_CREATE_DIRECTORIES:
|
||||
logError(LOG_INSTANCE, "Failed to delete server {} file directory due to an unknown error: {}/{}",
|
||||
server->getServerId(), (int) delete_result->error().error_type, delete_result->error().error_message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -460,7 +485,7 @@ void VirtualServerManager::tickHandshakeClients() {
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualServerManager::delete_server_in_db(ts::ServerId server_id) {
|
||||
void VirtualServerManager::delete_server_in_db(ts::ServerId server_id, bool data_only) {
|
||||
#define execute_delete(statement) \
|
||||
result = sql::command(this->handle->getSql(), statement, variable{":sid", server_id}).execute(); \
|
||||
if(!result) { \
|
||||
@@ -470,21 +495,60 @@ if(!result) { \
|
||||
|
||||
sql::result result{};
|
||||
|
||||
if(!data_only) {
|
||||
execute_delete("DELETE FROM `servers` WHERE `serverId` = :sid");
|
||||
}
|
||||
|
||||
execute_delete("DELETE FROM `tokens` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `properties` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `permissions` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `clients` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `channels` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `bannedClients` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `ban_trigger` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `groups` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `assignedGroups` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `servers` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `complains` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `letters` WHERE `serverId` = :sid");
|
||||
|
||||
execute_delete("DELETE FROM `musicbots` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `conversations` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `conversation_blocks` WHERE `server_id` = :sid");
|
||||
|
||||
execute_delete("DELETE FROM `playlists` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `playlist_songs` WHERE `serverId` = :sid");
|
||||
|
||||
execute_delete("DELETE FROM `conversations` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `conversation_blocks` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `ban_trigger` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `clients_server` WHERE `server_id` = :sid");
|
||||
}
|
||||
|
||||
#define execute_change(table, column) \
|
||||
result = sql::command(this->handle->getSql(), "UPDATE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \
|
||||
variable{":osid", old_id}, variable{":nsid", new_id}).execute(); \
|
||||
if(!result) { \
|
||||
logWarning(LOG_INSTANCE, "Failed to execute server id change on table {} (column {}): {}", table, column, result.fmtStr()); \
|
||||
result = sql::result{}; \
|
||||
}
|
||||
|
||||
void VirtualServerManager::change_server_id_in_db(ts::ServerId old_id, ts::ServerId new_id) {
|
||||
sql::result result{};
|
||||
|
||||
|
||||
execute_change("tokens", "serverId");
|
||||
execute_change("properties", "serverId");
|
||||
execute_change("permissions", "serverId");
|
||||
execute_change("channels", "serverId");
|
||||
execute_change("bannedClients", "serverId");
|
||||
execute_change("groups", "serverId");
|
||||
execute_change("assignedGroups", "serverId");
|
||||
execute_change("complains", "serverId");
|
||||
execute_change("letters", "serverId");
|
||||
execute_change("musicbots", "serverId");
|
||||
execute_change("playlists", "serverId");
|
||||
execute_change("playlist_songs", "serverId");
|
||||
|
||||
execute_change("conversations", "server_id");
|
||||
execute_change("conversation_blocks", "server_id");
|
||||
execute_change("ban_trigger", "server_id");
|
||||
execute_change("clients_server", "server_id");
|
||||
|
||||
execute_change("servers", "serverId");
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <deque>
|
||||
#include <EventLoop.h>
|
||||
#include "client/voice/PrecomputedPuzzles.h"
|
||||
#include "src/server/PrecomputedPuzzles.h"
|
||||
#include "server/VoiceIOManager.h"
|
||||
#include "VirtualServer.h"
|
||||
#include <query/command3.h>
|
||||
@@ -28,6 +28,16 @@ namespace ts::server {
|
||||
STOPPING
|
||||
};
|
||||
|
||||
enum struct SnapshotDeployResult {
|
||||
SUCCESS,
|
||||
|
||||
REACHED_SOFTWARE_SERVER_LIMIT,
|
||||
REACHED_CONFIG_SERVER_LIMIT,
|
||||
REACHED_SERVER_ID_LIMIT,
|
||||
|
||||
CUSTOM_ERROR /* error message set */
|
||||
};
|
||||
|
||||
explicit VirtualServerManager(InstanceHandler*);
|
||||
~VirtualServerManager();
|
||||
|
||||
@@ -54,10 +64,9 @@ namespace ts::server {
|
||||
void executeAutostart();
|
||||
void shutdownAll(const std::string&);
|
||||
|
||||
//Dotn use shared_ptr references to keep sure that they be hold in memory
|
||||
//Don't use shared_ptr references to keep sure that they be hold in memory
|
||||
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
|
||||
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
|
||||
bool deploy_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
SnapshotDeployResult deploy_snapshot(std::string& /* error */, std::shared_ptr<VirtualServer>& /* target server */, const command_parser& /* source */);
|
||||
|
||||
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
|
||||
|
||||
@@ -74,6 +83,7 @@ namespace ts::server {
|
||||
}
|
||||
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
|
||||
|
||||
/* This must be recursive */
|
||||
threads::Mutex server_create_lock;
|
||||
|
||||
State getState() { return this->state; }
|
||||
@@ -97,12 +107,9 @@ namespace ts::server {
|
||||
|
||||
void tickHandshakeClients();
|
||||
|
||||
void delete_server_in_db(ServerId /* server id */);
|
||||
void delete_server_in_db(ServerId /* server id */, bool /* data only */);
|
||||
void change_server_id_in_db(ServerId /* old id */, ServerId /* new id */);
|
||||
|
||||
/* methods used to preprocess a snapshot */
|
||||
bool deploy_ts3_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
bool deploy_teaspeak_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
/* actual deploy method */
|
||||
bool deploy_raw_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, const std::string& /* hash */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */);
|
||||
bool try_deploy_snapshot(std::string& /* error */, ServerId /* target server id */, ServerId /* logging server id */, const command_parser& /* source */);
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright (c) 2019 German Mendez Bravo (Kronuz)
|
||||
* Copyright (c) 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
* A btree::map<> implements the STL unique sorted associative container
|
||||
* interface and the pair associative container interface (a.k.a map<>) using a
|
||||
* btree. See btree.h for details of the btree implementation and caveats.
|
||||
*/
|
||||
|
||||
#ifndef BTREE_MAP_H__
|
||||
#define BTREE_MAP_H__
|
||||
|
||||
#include "btree.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace btree {
|
||||
|
||||
// A common base class for map and safe_map.
|
||||
template <typename Tree>
|
||||
class btree_map_container : public btree_unique_container<Tree> {
|
||||
typedef btree_map_container<Tree> self_type;
|
||||
typedef btree_unique_container<Tree> super_type;
|
||||
|
||||
public:
|
||||
typedef typename Tree::key_type key_type;
|
||||
typedef typename Tree::data_type data_type;
|
||||
typedef typename Tree::value_type value_type;
|
||||
typedef typename Tree::mapped_type mapped_type;
|
||||
typedef typename Tree::key_compare key_compare;
|
||||
typedef typename Tree::allocator_type allocator_type;
|
||||
typedef typename Tree::iterator iterator;
|
||||
typedef typename Tree::const_iterator const_iterator;
|
||||
|
||||
public:
|
||||
// Default constructor.
|
||||
btree_map_container(const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(comp, alloc) {
|
||||
}
|
||||
|
||||
// Copy constructor.
|
||||
btree_map_container(const self_type& x)
|
||||
: super_type(x) {
|
||||
}
|
||||
|
||||
// Range constructor.
|
||||
template <class InputIterator>
|
||||
btree_map_container(InputIterator b, InputIterator e,
|
||||
const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(b, e, comp, alloc) {
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
|
||||
return this->__tree.emplace_unique_key_args(key,
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(key),
|
||||
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
std::pair<iterator, bool> try_emplace(key_type&& key, Args&&... args) {
|
||||
return this->__tree.emplace_unique_key_args(key,
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(std::move(key)),
|
||||
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) {
|
||||
return this->__tree.emplace_hint_unique_key_args(hint, key,
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(key),
|
||||
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) {
|
||||
return this->__tree.emplace_hint_unique_key_args(hint, key,
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(std::move(key)),
|
||||
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// Access specified element with bounds checking.
|
||||
mapped_type& at(const key_type& key) {
|
||||
auto it = this->find(key);
|
||||
if (it == this->end()) {
|
||||
throw std::out_of_range("map::at: key not found");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
const mapped_type& at(const key_type& key) const {
|
||||
auto it = this->find(key);
|
||||
if (it == this->end()) {
|
||||
throw std::out_of_range("map::at: key not found");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Insertion routines.
|
||||
data_type& operator[](const key_type& key) {
|
||||
return this->try_emplace(key).first->second;
|
||||
}
|
||||
|
||||
data_type& operator[](key_type&& key) {
|
||||
return this->try_emplace(std::move(key)).first->second;
|
||||
}
|
||||
};
|
||||
|
||||
// The map class is needed mainly for its constructors.
|
||||
template <typename Key, typename Value,
|
||||
typename Compare = std::less<Key>,
|
||||
typename Alloc = std::allocator<std::pair<const Key, Value>>,
|
||||
int TargetNodeSize = 256>
|
||||
class map : public btree_map_container<
|
||||
btree<btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize>>> {
|
||||
|
||||
typedef map<Key, Value, Compare, Alloc, TargetNodeSize> self_type;
|
||||
typedef btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize> params_type;
|
||||
typedef btree<params_type> btree_type;
|
||||
typedef btree_map_container<btree_type> super_type;
|
||||
|
||||
public:
|
||||
typedef typename btree_type::key_compare key_compare;
|
||||
typedef typename btree_type::allocator_type allocator_type;
|
||||
|
||||
public:
|
||||
// Default constructor.
|
||||
map(const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(comp, alloc) {
|
||||
}
|
||||
|
||||
// Copy constructor.
|
||||
map(const self_type& x)
|
||||
: super_type(x) {
|
||||
}
|
||||
|
||||
// Range constructor.
|
||||
template <class InputIterator>
|
||||
map(InputIterator b, InputIterator e,
|
||||
const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(b, e, comp, alloc) {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace btree
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator==(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
|
||||
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator<(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
|
||||
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator!=(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator>(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator>=(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator<=(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
|
||||
return !(rhs < lhs);
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
inline void swap(btree::map<K, V, C, A, N>& x, btree::map<K, V, C, A, N>& y) {
|
||||
x.swap(y);
|
||||
}
|
||||
|
||||
namespace btree {
|
||||
|
||||
// The multimap class is needed mainly for its constructors.
|
||||
template <typename Key, typename Value,
|
||||
typename Compare = std::less<Key>,
|
||||
typename Alloc = std::allocator<std::pair<const Key, Value> >,
|
||||
int TargetNodeSize = 256>
|
||||
class multimap : public btree_multi_container<
|
||||
btree<btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize> > > {
|
||||
|
||||
typedef multimap<Key, Value, Compare, Alloc, TargetNodeSize> self_type;
|
||||
typedef btree_map_params< Key, Value, Compare, Alloc, TargetNodeSize> params_type;
|
||||
typedef btree<params_type> btree_type;
|
||||
typedef btree_multi_container<btree_type> super_type;
|
||||
|
||||
public:
|
||||
typedef typename btree_type::key_compare key_compare;
|
||||
typedef typename btree_type::allocator_type allocator_type;
|
||||
typedef typename btree_type::data_type data_type;
|
||||
typedef typename btree_type::mapped_type mapped_type;
|
||||
|
||||
public:
|
||||
// Default constructor.
|
||||
multimap(const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(comp, alloc) {
|
||||
}
|
||||
|
||||
// Copy constructor.
|
||||
multimap(const self_type& x)
|
||||
: super_type(x) {
|
||||
}
|
||||
|
||||
// Range constructor.
|
||||
template <class InputIterator>
|
||||
multimap(InputIterator b, InputIterator e,
|
||||
const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(b, e, comp, alloc) {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace btree
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator==(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
|
||||
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator<(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
|
||||
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator!=(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator>(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator>=(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
bool operator<=(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
|
||||
return !(rhs < lhs);
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename C, typename A, int N>
|
||||
inline void swap(btree::multimap<K, V, C, A, N>& x, btree::multimap<K, V, C, A, N>& y) {
|
||||
x.swap(y);
|
||||
}
|
||||
|
||||
#endif // BTREE_MAP_H__
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2019 German Mendez Bravo (Kronuz)
|
||||
* Copyright (c) 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
* A btree::set<> implements the STL unique sorted associative container
|
||||
* interface (a.k.a set<>) using a btree. See btree.h for details of the btree
|
||||
* implementation and caveats.
|
||||
*/
|
||||
|
||||
#ifndef BTREE_SET_H__
|
||||
#define BTREE_SET_H__
|
||||
|
||||
#include "btree.h"
|
||||
|
||||
namespace btree {
|
||||
|
||||
// The set class is needed mainly for its constructors.
|
||||
template <typename Key,
|
||||
typename Compare = std::less<Key>,
|
||||
typename Alloc = std::allocator<Key>,
|
||||
int TargetNodeSize = 256>
|
||||
class set : public btree_unique_container<
|
||||
btree<btree_set_params<Key, Compare, Alloc, TargetNodeSize> > > {
|
||||
|
||||
typedef set<Key, Compare, Alloc, TargetNodeSize> self_type;
|
||||
typedef btree_set_params<Key, Compare, Alloc, TargetNodeSize> params_type;
|
||||
typedef btree<params_type> btree_type;
|
||||
typedef btree_unique_container<btree_type> super_type;
|
||||
|
||||
public:
|
||||
typedef typename btree_type::key_compare key_compare;
|
||||
typedef typename btree_type::allocator_type allocator_type;
|
||||
|
||||
public:
|
||||
// Default constructor.
|
||||
set(const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(comp, alloc) {
|
||||
}
|
||||
|
||||
// Copy constructor.
|
||||
set(const self_type& x)
|
||||
: super_type(x) {
|
||||
}
|
||||
|
||||
// Range constructor.
|
||||
template <class InputIterator>
|
||||
set(InputIterator b, InputIterator e,
|
||||
const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(b, e, comp, alloc) {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace btree
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator==(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
|
||||
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator<(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
|
||||
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator!=(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator>(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator>=(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator<=(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
|
||||
return !(rhs < lhs);
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
inline void swap(btree::set<K, C, A, N>& x, btree::set<K, C, A, N>& y) {
|
||||
x.swap(y);
|
||||
}
|
||||
|
||||
namespace btree {
|
||||
|
||||
// The multiset class is needed mainly for its constructors.
|
||||
template <typename Key,
|
||||
typename Compare = std::less<Key>,
|
||||
typename Alloc = std::allocator<Key>,
|
||||
int TargetNodeSize = 256>
|
||||
class multiset : public btree_multi_container<
|
||||
btree<btree_set_params<Key, Compare, Alloc, TargetNodeSize> > > {
|
||||
|
||||
typedef multiset<Key, Compare, Alloc, TargetNodeSize> self_type;
|
||||
typedef btree_set_params<Key, Compare, Alloc, TargetNodeSize> params_type;
|
||||
typedef btree<params_type> btree_type;
|
||||
typedef btree_multi_container<btree_type> super_type;
|
||||
|
||||
public:
|
||||
typedef typename btree_type::key_compare key_compare;
|
||||
typedef typename btree_type::allocator_type allocator_type;
|
||||
|
||||
public:
|
||||
// Default constructor.
|
||||
multiset(const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(comp, alloc) {
|
||||
}
|
||||
|
||||
// Copy constructor.
|
||||
multiset(const self_type& x)
|
||||
: super_type(x) {
|
||||
}
|
||||
|
||||
// Range constructor.
|
||||
template <class InputIterator>
|
||||
multiset(InputIterator b, InputIterator e,
|
||||
const key_compare& comp = key_compare(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: super_type(b, e, comp, alloc) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace btree
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator==(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
|
||||
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator<(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
|
||||
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator!=(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator>(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator>=(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
bool operator<=(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
|
||||
return !(rhs < lhs);
|
||||
}
|
||||
|
||||
template <typename K, typename C, typename A, int N>
|
||||
inline void swap(btree::multiset<K, C, A, N>& x, btree::multiset<K, C, A, N>& y) {
|
||||
x.swap(y);
|
||||
}
|
||||
|
||||
#endif // BTREE_SET_H__
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "misc/rnd.h"
|
||||
#include "src/VirtualServer.h"
|
||||
#include "src/client/ConnectedClient.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "../manager/ConversationManager.h"
|
||||
|
||||
@@ -481,6 +480,7 @@ bool ServerChannelTree::validateChannelNames() {
|
||||
bool ServerChannelTree::validateChannelIcons() {
|
||||
for(const auto &channel : this->channels()) {
|
||||
auto iconId = (IconId) channel->properties()[property::CHANNEL_ICON_ID];
|
||||
#if 0
|
||||
if(iconId != 0 && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
|
||||
logMessage(this->getServerId(), "[FILE] Missing channel icon (" + to_string(iconId) + ").");
|
||||
if(config::server::delete_missing_icon_permissions) {
|
||||
@@ -488,6 +488,7 @@ bool ServerChannelTree::validateChannelIcons() {
|
||||
channel->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -528,6 +529,8 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
|
||||
|
||||
//assert(type != 0xFF);
|
||||
assert(channelId != 0);
|
||||
if(channelId == 0)
|
||||
return 0;
|
||||
|
||||
auto channel = std::make_shared<ServerChannel>(parentId, channelId);
|
||||
auto server = this->server.lock();
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "src/VirtualServer.h"
|
||||
#include "voice/VoiceClient.h"
|
||||
#include "../server/VoiceServer.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "../InstanceHandler.h"
|
||||
#include "ConnectedClient.h"
|
||||
|
||||
@@ -37,6 +36,14 @@ ConnectedClient::~ConnectedClient() {
|
||||
memtrack::freed<ConnectedClient>(this);
|
||||
}
|
||||
|
||||
bool ConnectedClient::loadDataForCurrentServer() {
|
||||
auto result = DataClient::loadDataForCurrentServer();
|
||||
if(!result) return false;
|
||||
|
||||
this->properties()[property::CONNECTION_CLIENT_IP] = this->getLoggingPeerIp();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectionInfoData> ConnectedClient::request_connection_info(const std::shared_ptr<ConnectedClient> &receiver, bool& send_temp) {
|
||||
auto& info = this->connection_info;
|
||||
|
||||
@@ -143,7 +150,8 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool
|
||||
iconId = value.value;
|
||||
}
|
||||
logTrace(this->getServerId(), "[CLIENT] Updating client icon from " + to_string(this->properties()[property::CLIENT_ICON_ID].as<IconId>()) + " to " + to_string(iconId));
|
||||
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId){
|
||||
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId) {
|
||||
#if 0
|
||||
if(server_ref && iconId != 0) {
|
||||
auto dir = serverInstance->getFileServer()->iconDirectory(server_ref);
|
||||
if(!serverInstance->getFileServer()->iconExists(server_ref, iconId)) {
|
||||
@@ -151,6 +159,7 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool
|
||||
iconId = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId) {
|
||||
this->properties()[property::CLIENT_ICON_ID] = (IconId) iconId;
|
||||
notifyList.emplace_back(property::CLIENT_ICON_ID);
|
||||
@@ -167,7 +176,7 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool
|
||||
block_flood = !permission::v2::permission_granted(1, permission_ignore_antiflood);
|
||||
if(server_ref)
|
||||
server_ref->notifyClientPropertyUpdates(_this.lock(), notifyList, notify_self);
|
||||
this->updateTalkRights(permission_talk_power.has_value ? permission_talk_power.value : 0);
|
||||
this->updateTalkRights(permission_talk_power);
|
||||
|
||||
if((this->channels_view_power != permission_channel_view_power || this->channels_ignore_view != permission_channel_ignore_view_power) && notify_self && this->currentChannel && server_ref) {
|
||||
this->channels_view_power = permission_channel_view_power;
|
||||
@@ -196,11 +205,11 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectedClient::updateTalkRights(permission::PermissionValue talk_power) {
|
||||
void ConnectedClient::updateTalkRights(permission::v2::PermissionFlaggedValue talk_power) {
|
||||
bool flag = false;
|
||||
flag |= this->properties()[property::CLIENT_IS_TALKER].as<bool>();
|
||||
if(!flag && this->currentChannel) {
|
||||
flag = this->currentChannel->talk_power_granted({talk_power, talk_power != permNotGranted});
|
||||
flag = this->currentChannel->talk_power_granted(talk_power);
|
||||
}
|
||||
this->allowedToTalk = flag;
|
||||
}
|
||||
@@ -574,16 +583,16 @@ bool ConnectedClient::notifyClientNeededPermissions() {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void write_command_result_error(ts::command_builder_bulk bulk, const command_result& result) {
|
||||
bulk.put_unchecked("id", (uint32_t) result.error_code());
|
||||
inline void write_command_result_error(ts::command_builder_bulk bulk, const command_result& result, const std::string& errorCodeKey) {
|
||||
bulk.put_unchecked(errorCodeKey, (uint32_t) result.error_code());
|
||||
bulk.put_unchecked("msg", findError(result.error_code()).message);
|
||||
if(result.is_permission_error())
|
||||
bulk.put_unchecked("failed_permid", (uint32_t) result.permission_id());
|
||||
}
|
||||
|
||||
inline void write_command_result_detailed(ts::command_builder_bulk bulk, const command_result& result) {
|
||||
inline void write_command_result_detailed(ts::command_builder_bulk bulk, const command_result& result, const std::string& errorCodeKey) {
|
||||
auto details = result.details();
|
||||
bulk.put_unchecked("id", (uint32_t) details->error_id);
|
||||
bulk.put_unchecked(errorCodeKey, (uint32_t) details->error_id);
|
||||
bulk.put_unchecked("msg", findError(details->error_id).message);
|
||||
|
||||
for(const auto& extra : details->extra_properties)
|
||||
@@ -593,43 +602,7 @@ inline void write_command_result_detailed(ts::command_builder_bulk bulk, const c
|
||||
bool ConnectedClient::notifyError(const command_result& result, const std::string& retCode) {
|
||||
ts::command_builder command{"error"};
|
||||
|
||||
switch(result.type()) {
|
||||
case command_result_type::error:
|
||||
write_command_result_error(command.bulk(0), result);
|
||||
break;
|
||||
case command_result_type::detailed:
|
||||
write_command_result_detailed(command.bulk(0), result);
|
||||
break;
|
||||
|
||||
case command_result_type::bulked: {
|
||||
auto bulks = result.bulks();
|
||||
command.reserve_bulks(bulks->size());
|
||||
for(size_t index{0}; index < bulks->size(); index++) {
|
||||
auto& entry = bulks->at(index);
|
||||
switch (entry.type()) {
|
||||
case command_result_type::error:
|
||||
write_command_result_error(command.bulk(index), entry);
|
||||
break;
|
||||
case command_result_type::detailed:
|
||||
write_command_result_detailed(command.bulk(index), entry);
|
||||
break;
|
||||
case command_result_type::bulked:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(bulks->empty()) {
|
||||
logWarning(this->getServerId(), "{} Trying to send empty error bulk.", CLIENT_STR_LOG_PREFIX_(this));
|
||||
command.put_unchecked(0, "id", (uint32_t) error::ok);
|
||||
command.put_unchecked(0, "msg", findError(error::ok).message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
this->writeCommandResult(command, result);
|
||||
if(!retCode.empty())
|
||||
command.put_unchecked(0, "return_code", retCode);
|
||||
|
||||
@@ -637,6 +610,10 @@ bool ConnectedClient::notifyError(const command_result& result, const std::strin
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectedClient::writeCommandResult(ts::command_builder &cmd_builder, const command_result &result, const std::string& errorCodeKey) {
|
||||
result.build_error_response(cmd_builder, errorCodeKey);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<ViewEntry> pop_view_entry(std::deque<std::shared_ptr<ViewEntry>>& pool, ChannelId id) {
|
||||
for(auto it = pool.begin(); it != pool.end(); it++) {
|
||||
if((*it)->channelId() == id) {
|
||||
@@ -829,6 +806,30 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
|
||||
command_result result;
|
||||
try {
|
||||
result.reset(this->handleCommand(cmd));
|
||||
} catch(command_value_cast_failed& ex){
|
||||
auto message = ex.key() + " at " + std::to_string(ex.index()) + " could not be casted to " + ex.target_type().name();
|
||||
if(disconnectOnFail) {
|
||||
this->disconnect(message);
|
||||
return false;
|
||||
} else {
|
||||
result.reset(command_result{error::parameter_convert, message});
|
||||
}
|
||||
} catch(command_bulk_index_out_of_bounds_exception& ex){
|
||||
auto message = "missing bulk for index " + std::to_string(ex.index());
|
||||
if(disconnectOnFail) {
|
||||
this->disconnect(message);
|
||||
return false;
|
||||
} else {
|
||||
result.reset(command_result{error::parameter_invalid_count, message});
|
||||
}
|
||||
} catch(command_value_missing_exception& ex){
|
||||
auto message = "missing value for " + ex.key() + (ex.bulk_index() == std::string::npos ? "" : " at " + std::to_string(ex.bulk_index()));
|
||||
if(disconnectOnFail) {
|
||||
this->disconnect(message);
|
||||
return false;
|
||||
} else {
|
||||
result.reset(command_result{error::parameter_missing, message});
|
||||
}
|
||||
} catch(invalid_argument& ex){
|
||||
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what());
|
||||
if(disconnectOnFail) {
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
#define CLIENT_STR_LOG_PREFIX CLIENT_STR_LOG_PREFIX_(this)
|
||||
|
||||
#define CMD_REQ_SERVER \
|
||||
if(!this->server) return command_result{error::server_invalid_id};
|
||||
do { \
|
||||
if(!this->server) { \
|
||||
return command_result{error::server_invalid_id}; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* TODO: Play lock the server here with read? So the client dosn't get kicked within that moment */
|
||||
#define CMD_REF_SERVER(variable_name) \
|
||||
@@ -33,8 +37,10 @@ if(!cmd[0].has(parm)) return command_result{error::parameter_not_found};
|
||||
|
||||
//the message here is show to the manager!
|
||||
#define CMD_CHK_AND_INC_FLOOD_POINTS(num) \
|
||||
this->increaseFloodPoints(num); \
|
||||
if(this->shouldFloodBlock()) return command_result{error::ban_flooding};
|
||||
do {\
|
||||
this->increaseFloodPoints(num); \
|
||||
if(this->shouldFloodBlock()) return command_result{error::ban_flooding}; \
|
||||
} while(0)
|
||||
|
||||
#define CMD_CHK_PARM_COUNT(count) \
|
||||
if(cmd.bulkCount() != count) return command_result{error::parameter_invalid_count};
|
||||
@@ -116,6 +122,7 @@ namespace ts {
|
||||
|
||||
/** Notifies general stuff **/
|
||||
virtual bool notifyError(const command_result&, const std::string& retCode = "");
|
||||
virtual void writeCommandResult(ts::command_builder&, const command_result&, const std::string& errorCodeKey = "id");
|
||||
|
||||
/** Notifies (after request) */
|
||||
bool sendNeededPermissions(bool /* force an update */); /* invoke this because it dosn't spam the client */
|
||||
@@ -247,7 +254,7 @@ namespace ts {
|
||||
std::shared_ptr<ConnectionInfoData> request_connection_info(const std::shared_ptr<ConnectedClient> & /* receiver */, bool& /* send temporary (no client response yet) */);
|
||||
|
||||
virtual void updateChannelClientProperties(bool /* lock channel tree */, bool /* notify our self */);
|
||||
void updateTalkRights(permission::PermissionValue talk_power);
|
||||
void updateTalkRights(permission::v2::PermissionFlaggedValue talk_power);
|
||||
|
||||
virtual std::shared_ptr<BanRecord> resolveActiveBan(const std::string& ip_address);
|
||||
|
||||
@@ -368,6 +375,8 @@ namespace ts {
|
||||
std::weak_ptr<MusicClient> subscribed_bot;
|
||||
std::weak_ptr<ts::music::Playlist> _subscribed_playlist{};
|
||||
|
||||
bool loadDataForCurrentServer() override;
|
||||
|
||||
virtual void tick(const std::chrono::system_clock::time_point &time);
|
||||
//Locked by everything who has something todo with command handling
|
||||
threads::Mutex command_lock; /* Note: This mutex must be recursive! */
|
||||
@@ -410,13 +419,13 @@ namespace ts {
|
||||
command_result handleCommandChannelDelPerm(Command&);
|
||||
|
||||
//Server group manager management
|
||||
command_result handleCommandServerGroupCopy(Command&);
|
||||
command_result handleCommandServerGroupAdd(Command&);
|
||||
command_result handleCommandServerGroupCopy(Command&);
|
||||
command_result handleCommandServerGroupRename(Command&);
|
||||
command_result handleCommandServerGroupDel(Command&);
|
||||
command_result handleCommandServerGroupClientList(Command&);
|
||||
command_result handleCommandServerGroupDelClient(Command&);
|
||||
command_result handleCommandServerGroupAddClient(Command&);
|
||||
command_result handleCommandServerGroupDelClient(Command&);
|
||||
command_result handleCommandServerGroupPermList(Command&);
|
||||
command_result handleCommandServerGroupAddPerm(Command&);
|
||||
command_result handleCommandServerGroupDelPerm(Command&);
|
||||
@@ -453,11 +462,10 @@ namespace ts {
|
||||
command_result handleCommandFTDeleteFile(Command&);
|
||||
command_result handleCommandFTInitUpload(Command&);
|
||||
command_result handleCommandFTInitDownload(Command&);
|
||||
command_result handleCommandFTGetFileInfo(Command&);
|
||||
//CMD_TODO handleCommandFTGetFileInfo -> 5 points
|
||||
//CMD_TODO handleCommandFTStop -> 5 points
|
||||
//CMD_TODO handleCommandFTRenameFile -> 5 points
|
||||
//CMD_TODO handleCommandFTList -> 5 points
|
||||
command_result handleCommandFTGetFileInfo(Command&);
|
||||
command_result handleCommandFTRenameFile(Command&);
|
||||
command_result handleCommandFTList(Command&);
|
||||
command_result handleCommandFTStop(Command&);
|
||||
|
||||
command_result handleCommandBanList(Command&);
|
||||
command_result handleCommandBanAdd(Command&);
|
||||
@@ -592,7 +600,10 @@ namespace ts {
|
||||
command_result handleCommandConversationMessageDelete(Command&);
|
||||
|
||||
command_result handleCommandLogView(Command&);
|
||||
//CMD_TODO handleCommandLogAdd
|
||||
command_result handleCommandLogQuery(Command&);
|
||||
command_result handleCommandLogAdd(Command&);
|
||||
|
||||
command_result handleCommandListFeatureSupport(Command &cmd);
|
||||
|
||||
//handleCommandClientSiteReport() -> return findError(0x00)
|
||||
//handleCommandChannelCreatePrivate() -> return findError(0x02)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <algorithm>
|
||||
#include "ConnectedClient.h"
|
||||
#include "voice/VoiceClient.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "../server/VoiceServer.h"
|
||||
#include "../InstanceHandler.h"
|
||||
#include "../server/QueryServer.h"
|
||||
@@ -21,9 +20,7 @@ using namespace ts;
|
||||
using namespace ts::server;
|
||||
using namespace ts::token;
|
||||
|
||||
extern ts::server::InstanceHandler* serverInstance;
|
||||
|
||||
#define INVOKER(command, invoker) \
|
||||
# define INVOKER(command, invoker) \
|
||||
do { \
|
||||
if(invoker) { \
|
||||
command["invokerid"] = invoker->getClientId(); \
|
||||
|
||||
@@ -391,8 +391,8 @@ bool ConnectedClient::handle_text_command(
|
||||
for(const auto& song : bot_playlist->list_songs()) {
|
||||
string invoker = "unknown";
|
||||
for(const auto& e : dbinfo) {
|
||||
if(e->cldbid == song->invoker) {
|
||||
invoker = "[URL=client://0/" + e->uniqueId + "~" + e->lastName + "]" + e->lastName + "[/URL]";
|
||||
if(e->client_database_id == song->invoker) {
|
||||
invoker = "[URL=client://0/" + e->client_unique_id + "~" + e->client_nickname + "]" + e->client_nickname + "[/URL]";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -639,19 +639,19 @@ bool ConnectedClient::handle_text_command(
|
||||
protocol::PacketTypeInfo::Ping,
|
||||
protocol::PacketTypeInfo::Pong}) {
|
||||
|
||||
auto id = vc->getConnection()->getPacketIdManager().currentPacketId(type);
|
||||
auto gen = vc->getConnection()->getPacketIdManager().generationId(type);
|
||||
auto& genestis = vc->getConnection()->get_incoming_generation_estimators();
|
||||
//auto id = vc->getConnection()->getPacketIdManager().currentPacketId(type);
|
||||
//auto gen = vc->getConnection()->getPacketIdManager().generationId(type);
|
||||
//auto& genestis = vc->getConnection()->get_incoming_generation_estimators();
|
||||
|
||||
send_message(_this.lock(), " OUT " + type.name() + " => generation: " + to_string(gen) + " id: " + to_string(id));
|
||||
send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
|
||||
//send_message(_this.lock(), " OUT " + type.name() + " => generation: " + to_string(gen) + " id: " + to_string(id));
|
||||
//send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
|
||||
}
|
||||
return true;
|
||||
} else if(TARG(0, "ping")) {
|
||||
} else if(TARG(0, "rtt")) {
|
||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||
if(!vc) return false;
|
||||
|
||||
auto& ack = vc->connection->getAcknowledgeManager();
|
||||
auto& ack = vc->connection->packet_encoder().acknowledge_manager();
|
||||
send_message(_this.lock(), "Command retransmission values:");
|
||||
send_message(_this.lock(), " RTO : " + std::to_string(ack.current_rto()));
|
||||
send_message(_this.lock(), " RTTVAR: " + std::to_string(ack.current_rttvar()));
|
||||
@@ -668,17 +668,29 @@ bool ConnectedClient::handle_text_command(
|
||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||
if(!vc) return false;
|
||||
|
||||
/*
|
||||
auto& genestis = vc->getConnection()->get_incoming_generation_estimators();
|
||||
if(type >= genestis.size()) {
|
||||
send_message(_this.lock(), "Invalid type");
|
||||
return true;
|
||||
}
|
||||
genestis[type].set_last_state(pid, generation);
|
||||
*/
|
||||
} catch(std::exception& ex) {
|
||||
send_message(_this.lock(), "Failed to parse argument");
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
} else if(TARG(0, "ping")) {
|
||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||
if(!vc) return false;
|
||||
|
||||
auto ping_handler = vc->getConnection()->ping_handler();
|
||||
send_message(this->ref(), "Ping: " + std::to_string(ping_handler.current_ping().count()));
|
||||
|
||||
auto last_response_ms = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now() - ping_handler.last_ping_response()).count();
|
||||
send_message(this->ref(), "Last ping response: " + std::to_string(last_response_ms));
|
||||
return true;
|
||||
} else if(TARG(0, "disconnect")) {
|
||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||
if(!vc) return false;
|
||||
@@ -692,8 +704,8 @@ bool ConnectedClient::handle_text_command(
|
||||
if(!vc) return false;
|
||||
|
||||
send_message(_this.lock(), "I lost your IP address. I'm so dump :)");
|
||||
vc->connection->reset_remote_address();
|
||||
memset(&vc->remote_address, 0, sizeof(vc->remote_address));
|
||||
memset(&vc->address_info, 0, sizeof(vc->address_info));
|
||||
send_message(_this.lock(), "Hey, we got the address back");
|
||||
return true;
|
||||
} else if(TARG(0, "fb")) {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#include <log/LogUtils.h>
|
||||
#include <src/client/voice/VoiceClient.h>
|
||||
#include <misc/sassert.h>
|
||||
#include <misc/hex.h>
|
||||
#include "DataClient.h"
|
||||
#include "ConnectedClient.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "misc/base64.h"
|
||||
#include <misc/base64.h>
|
||||
|
||||
#include "./DataClient.h"
|
||||
#include "../InstanceHandler.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ts;
|
||||
@@ -23,52 +21,78 @@ DataClient::~DataClient() {
|
||||
this->_properties = nullptr;
|
||||
}
|
||||
|
||||
bool DataClient::loadDataForCurrentServer() { //TODO for query
|
||||
if(this->getUid().empty()) return false;
|
||||
constexpr static std::string_view kClientLoadCommand{R"(
|
||||
SELECT
|
||||
`client_database_id`,
|
||||
`client_created`,
|
||||
|
||||
`client_last_connected`,
|
||||
`client_total_connections`,
|
||||
|
||||
`client_month_upload`,
|
||||
`client_month_download`,
|
||||
|
||||
`client_total_upload`,
|
||||
`client_total_download`
|
||||
FROM `clients_server` WHERE `server_id` = :sid AND `client_unique_id` = :uid
|
||||
)"};
|
||||
|
||||
bool DataClient::loadDataForCurrentServer() {
|
||||
auto uniqueId = this->getUid();
|
||||
if(uniqueId.empty())
|
||||
return false;
|
||||
|
||||
auto ref_server = this->server;
|
||||
auto server_id = ref_server ? ref_server->getServerId() : 0;
|
||||
|
||||
properties()[property::CLIENT_DATABASE_ID] = 0;
|
||||
properties()[property::CLIENT_CREATED] = 0;
|
||||
properties()[property::CLIENT_TOTALCONNECTIONS] = 0;
|
||||
this->clientPermissions = std::make_shared<permission::v2::PermissionManager>();
|
||||
|
||||
auto properties = this->properties();
|
||||
sql::command{this->sql, std::string{kClientLoadCommand}, variable{":uid", uniqueId}, variable{":sid", server_id}}.query([&](int length, std::string* values, std::string* names) {
|
||||
auto index{0};
|
||||
|
||||
ClientDbId client_db_id = 0;
|
||||
sql::command(this->sql, "SELECT `cldbid`,`firstConnect`,`connections` FROM `clients` WHERE `serverId` = :sid AND `clientUid` = :uid LIMIT 1",
|
||||
variable{":sid", server_id},
|
||||
variable{":uid", this->getUid()}
|
||||
).query([&](DataClient* cl, int length, string* values, string* names){
|
||||
for (int index = 0; index < length; index++) {
|
||||
try {
|
||||
if (names[index] == "cldbid") {
|
||||
client_db_id = stoull(values[index]);
|
||||
} else if (names[index] == "firstConnect") {
|
||||
cl->properties()[property::CLIENT_CREATED] = values[index];
|
||||
} else if (names[index] == "connections") {
|
||||
cl->properties()[property::CLIENT_TOTALCONNECTIONS] = values[index];
|
||||
} else {
|
||||
logWarning(LOG_INSTANCE, "Received unknown column with name {} within client list", names[index]);
|
||||
}
|
||||
} catch(const std::exception& ex) {
|
||||
logError(server_id, "Failed to load client {} base properties from database. Colum parsing for column {} failed. Value: {}. Message: {}",
|
||||
this->getUid(),
|
||||
names[index],
|
||||
values[index],
|
||||
ex.what()
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
assert(names[index] == "client_database_id");
|
||||
properties[property::CLIENT_DATABASE_ID] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_created");
|
||||
properties[property::CLIENT_CREATED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_last_connected");
|
||||
properties[property::CLIENT_LASTCONNECTED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
properties[property::CLIENT_TOTALCONNECTIONS] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_month_upload");
|
||||
properties[property::CLIENT_MONTH_BYTES_UPLOADED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_month_download");
|
||||
properties[property::CLIENT_MONTH_BYTES_DOWNLOADED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_total_upload");
|
||||
properties[property::CLIENT_TOTAL_BYTES_UPLOADED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_total_download");
|
||||
properties[property::CLIENT_TOTAL_BYTES_DOWNLOADED] = std::stoull(values[index++]);
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
logError(server_id, "Failed to load client {} base properties from database: {} (Index {})",
|
||||
this->getUid(),
|
||||
ex.what(),
|
||||
index - 1
|
||||
);
|
||||
properties[property::CLIENT_DATABASE_ID] = 0;
|
||||
}
|
||||
return 0;
|
||||
}, this);
|
||||
});
|
||||
|
||||
if(client_db_id == 0)
|
||||
if(this->properties()[property::CLIENT_DATABASE_ID].as<ClientDbId>() == 0)
|
||||
return false;
|
||||
|
||||
this->properties()[property::CLIENT_DATABASE_ID] = client_db_id; /* do this before the property saving (it saved the cldbid as well!)*/
|
||||
|
||||
//Load general properties
|
||||
deque<ts::PropertyWrapper> copied;
|
||||
std::deque<ts::PropertyWrapper> copied;
|
||||
for(const auto& prop : this->_properties->list_properties()){
|
||||
if((prop.type().flags & property::FLAG_GLOBAL) == 0) continue;
|
||||
if(prop.type().default_value == prop.value()) continue;
|
||||
@@ -81,28 +105,7 @@ bool DataClient::loadDataForCurrentServer() { //TODO for query
|
||||
return false;
|
||||
}
|
||||
|
||||
auto client_type = this->getType();
|
||||
if(client_type == CLIENT_TEAMSPEAK || ref_server) {
|
||||
this->_properties = serverInstance->databaseHelper()->loadClientProperties(ref_server, this->getClientDatabaseId(), client_type);
|
||||
} else {
|
||||
this->_properties = DatabaseHelper::default_properties_client(nullptr, client_type);
|
||||
|
||||
this->_properties->registerNotifyHandler([&, server_id, client_db_id](Property& prop){
|
||||
std::string query;
|
||||
if(prop.type() == property::CLIENT_TOTALCONNECTIONS)
|
||||
query = "UPDATE `clients` SET `connections` = :value WHERE `serverId` = :sid AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_NICKNAME)
|
||||
query = "UPDATE `clients` SET `lastName` = :value WHERE `serverId` = :sid AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_LASTCONNECTED)
|
||||
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :sid AND `cldbid` = :cldbid";
|
||||
else
|
||||
return;
|
||||
|
||||
debugMessage(server_id, "[Property] Updating general client table property for client {}. Key: {} Value: {}", client_db_id, prop.type().name, prop.value());
|
||||
sql::command(this->sql, query, variable{":sid", 0}, variable{":cldbid", client_db_id}, variable{":value", prop.value()}).executeLater()
|
||||
.waitAndGetLater(LOG_SQL_CMD, {1, "failed to update general client properties"});
|
||||
});
|
||||
}
|
||||
this->_properties = serverInstance->databaseHelper()->loadClientProperties(ref_server, this->getClientDatabaseId(), this->getType());
|
||||
|
||||
this->_properties->toggleSave(false);
|
||||
for(const auto& e : copied) {
|
||||
@@ -115,6 +118,7 @@ bool DataClient::loadDataForCurrentServer() { //TODO for query
|
||||
this->clientPermissions = serverInstance->databaseHelper()->loadClientPermissionManager(ref_server, this->getClientDatabaseId());
|
||||
|
||||
//Setup / fix stuff
|
||||
#if 0
|
||||
if(!this->properties()[property::CLIENT_FLAG_AVATAR].as<string>().empty()){
|
||||
if(
|
||||
!ref_server ||
|
||||
@@ -123,6 +127,7 @@ bool DataClient::loadDataForCurrentServer() { //TODO for query
|
||||
this->properties()[property::CLIENT_FLAG_AVATAR] = "";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ref_server)
|
||||
this->properties()[property::CLIENT_UNREAD_MESSAGES] = ref_server->letters->unread_letter_count(this->getUid());
|
||||
|
||||
@@ -2,24 +2,23 @@
|
||||
|
||||
#include "ConnectedClient.h"
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class InternalClient : public ConnectedClient {
|
||||
public:
|
||||
InternalClient(sql::SqlManager*, const std::shared_ptr<server::VirtualServer>&, std::string, bool);
|
||||
~InternalClient();
|
||||
namespace ts::server {
|
||||
class VirtualServer;
|
||||
class InternalClient : public ConnectedClient {
|
||||
public:
|
||||
InternalClient(sql::SqlManager*, const std::shared_ptr<VirtualServer>&, std::string, bool);
|
||||
~InternalClient();
|
||||
|
||||
void setSharedLock(const std::shared_ptr<ConnectedClient>& _this){
|
||||
assert(_this.get() == this);
|
||||
this->_this = _this;
|
||||
}
|
||||
void setSharedLock(const std::shared_ptr<ConnectedClient>& _this){
|
||||
assert(_this.get() == this);
|
||||
this->_this = _this;
|
||||
}
|
||||
|
||||
void sendCommand(const ts::Command &command, bool low) override;
|
||||
void sendCommand(const ts::command_builder &command, bool low) override;
|
||||
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
|
||||
bool disconnect(const std::string &reason) override;
|
||||
protected:
|
||||
void tick(const std::chrono::system_clock::time_point &time) override;
|
||||
};
|
||||
}
|
||||
void sendCommand(const ts::Command &command, bool low) override;
|
||||
void sendCommand(const ts::command_builder &command, bool low) override;
|
||||
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
|
||||
bool disconnect(const std::string &reason) override;
|
||||
protected:
|
||||
void tick(const std::chrono::system_clock::time_point &time) override;
|
||||
};
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
#include <memory>
|
||||
|
||||
#include <PermissionManager.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <ThreadPool/Timer.h>
|
||||
#include <regex>
|
||||
#include <src/build.h>
|
||||
#include <Properties.h>
|
||||
#include <src/client/command_handler/helpers.h>
|
||||
#include "src/channel/ClientChannelView.h"
|
||||
#include "SpeakingClient.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "StringVariable.h"
|
||||
#include "misc/timer.h"
|
||||
#include "../manager/ActionLogger.h"
|
||||
|
||||
using namespace std::chrono;
|
||||
using namespace ts;
|
||||
@@ -19,6 +22,9 @@ using namespace ts::protocol;
|
||||
|
||||
//#define PKT_LOG_VOICE
|
||||
//#define PKT_LOG_WHISPER
|
||||
constexpr static auto kMaxWhisperClientNameLength{30};
|
||||
constexpr static auto kWhisperClientUniqueIdLength{28}; /* base64 encoded SHA1 hash */
|
||||
constexpr static auto kWhisperMaxHeaderLength{2 + 2 + 1 + 2 + kWhisperClientUniqueIdLength + 1 + kMaxWhisperClientNameLength};
|
||||
|
||||
bool SpeakingClient::shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender) {
|
||||
//if(this->properties()[property::CLIENT_AWAY].as<bool>()) return false;
|
||||
@@ -123,7 +129,7 @@ enum WhisperType {
|
||||
CHANNEL_COMMANDER = 2,
|
||||
ALL = 3,
|
||||
|
||||
ECHO_TEXT = 0x10,
|
||||
ECHO = 0x10,
|
||||
};
|
||||
|
||||
enum WhisperTarget {
|
||||
@@ -149,42 +155,35 @@ inline bool update_whisper_error(std::chrono::system_clock::time_point& last) {
|
||||
//Server group => type := SERVER_GROUP and target_id := <server group id>
|
||||
//Channel group => type := CHANNEL_GROUP and target_id := <channel group id>
|
||||
//Channel commander => type := CHANNEL_COMMANDER and target_id := 0
|
||||
void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bool new_packet) {
|
||||
if(data.length() < 5) {
|
||||
void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& payload, bool new_packet, bool head) {
|
||||
if(payload.length() < 5) {
|
||||
this->disconnect("Invalid packet (Voice whisper)");
|
||||
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, data.length());
|
||||
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, payload.length());
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t offset = 0;
|
||||
auto vpacketId = be2le16((char*) data.data_ptr(), offset, &offset);
|
||||
auto codec = (uint8_t) data[offset++];
|
||||
uint16_t payload_offset{0};
|
||||
auto voice_packet_id = be2le16((char*) payload.data_ptr(), payload_offset, &payload_offset);
|
||||
auto voice_codec = (uint8_t) payload[payload_offset++];
|
||||
|
||||
VoicePacketFlags flags{};
|
||||
flags.head = false;
|
||||
flags.fragmented = false;
|
||||
flags.new_protocol = new_packet;
|
||||
std::deque<std::shared_ptr<SpeakingClient>> target_clients;
|
||||
|
||||
if(new_packet) {
|
||||
if(data.length() < 7) {
|
||||
if(payload.length() < 7) {
|
||||
this->disconnect("Invalid packet (Voice whisper | New)");
|
||||
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, data.length());
|
||||
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, payload.length());
|
||||
return;
|
||||
}
|
||||
|
||||
auto type = (WhisperType) data[offset++];
|
||||
auto target = (WhisperTarget) data[offset++];
|
||||
auto type_id = be2le64((char*) data.data_ptr(), offset, &offset);
|
||||
this->resetIdleTime();
|
||||
auto type = (WhisperType) payload[payload_offset++];
|
||||
auto target = (WhisperTarget) payload[payload_offset++];
|
||||
auto type_id = be2le64((char*) payload.data_ptr(), payload_offset, &payload_offset);
|
||||
|
||||
size_t data_length = data.length() - offset;
|
||||
#ifdef PKT_LOG_WHISPER
|
||||
logTrace(this->getServerId(), "{} Whisper data length: {}. Type: {}. Target: {}. Target ID: {}.", CLIENT_STR_LOG_PREFIX, data_length, type, target, type_id);
|
||||
#endif
|
||||
|
||||
deque<shared_ptr<SpeakingClient>> available_clients;
|
||||
if(type == WhisperType::ECHO_TEXT) {
|
||||
available_clients.push_back(dynamic_pointer_cast<SpeakingClient>(this->ref()));
|
||||
if(type == WhisperType::ECHO) {
|
||||
target_clients.push_back(dynamic_pointer_cast<SpeakingClient>(this->ref()));
|
||||
} else {
|
||||
for(const auto& client : this->server->getClients()) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(client);
|
||||
@@ -192,195 +191,235 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
|
||||
if(!speakingClient->currentChannel) continue;
|
||||
|
||||
if(type == WhisperType::ALL) {
|
||||
available_clients.push_back(speakingClient);
|
||||
target_clients.push_back(speakingClient);
|
||||
} else if(type == WhisperType::SERVER_GROUP) {
|
||||
if(type_id == 0)
|
||||
available_clients.push_back(speakingClient);
|
||||
target_clients.push_back(speakingClient);
|
||||
else {
|
||||
shared_lock client_lock(this->channel_lock);
|
||||
for(const auto& id : client->cached_server_groups) {
|
||||
if(id == type_id) {
|
||||
available_clients.push_back(speakingClient);
|
||||
target_clients.push_back(speakingClient);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(type == WhisperType::CHANNEL_GROUP) {
|
||||
if(client->cached_channel_group == type_id)
|
||||
available_clients.push_back(speakingClient);
|
||||
target_clients.push_back(speakingClient);
|
||||
} else if(type == WhisperType::CHANNEL_COMMANDER) {
|
||||
if(client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as<bool>())
|
||||
available_clients.push_back(speakingClient);
|
||||
target_clients.push_back(speakingClient);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(target == WhisperTarget::CHANNEL_CURRENT) {
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
return target->currentChannel != this->currentChannel;
|
||||
}), available_clients.end());
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_PARENT) {
|
||||
auto current_parent = this->currentChannel->parent();
|
||||
if(!current_parent) return;
|
||||
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
return target->currentChannel != current_parent;
|
||||
}), available_clients.end());
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_ALL_PARENT) {
|
||||
shared_ptr<BasicChannel> current_parent;
|
||||
{
|
||||
current_parent = this->currentChannel->parent();
|
||||
if(!current_parent) return;
|
||||
}
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
auto tmp_parent = current_parent;
|
||||
while(tmp_parent && tmp_parent != target->currentChannel)
|
||||
tmp_parent = tmp_parent->parent();
|
||||
return target->currentChannel != tmp_parent;
|
||||
}), available_clients.end());
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_FAMILY) {
|
||||
shared_ptr<BasicChannel> current = this->currentChannel;
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
auto tmp_current = target->currentChannel;
|
||||
while(tmp_current && tmp_current != current)
|
||||
tmp_current = tmp_current->parent();
|
||||
return current != tmp_current;
|
||||
}), available_clients.end());
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_COMPLETE_FAMILY) {
|
||||
shared_ptr<BasicChannel> current = this->currentChannel;
|
||||
while(current && current->parent()) current = current->parent();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
auto tmp_current = target->currentChannel;
|
||||
while(tmp_current && tmp_current != current)
|
||||
tmp_current = tmp_current->parent();
|
||||
return current != tmp_current;
|
||||
}), available_clients.end());
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_SUBCHANNELS) {
|
||||
shared_ptr<BasicChannel> current = this->currentChannel;
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
return target->currentChannel->parent() != current;
|
||||
}), available_clients.end());
|
||||
}), target_clients.end());
|
||||
}
|
||||
|
||||
auto self_lock = this->_this.lock();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
|
||||
}), available_clients.end());
|
||||
}
|
||||
}), target_clients.end());
|
||||
|
||||
if(available_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
if(target_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
|
||||
if(target_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Create the packet data
|
||||
char packet_buffer[OUT_WHISPER_PKT_OFFSET + data_length];
|
||||
if(offset < data.length())
|
||||
memcpy(&packet_buffer[OUT_WHISPER_PKT_OFFSET], &data[offset], data_length);
|
||||
|
||||
le2be16(vpacketId, packet_buffer, 0);
|
||||
le2be16(this->getClientId(), packet_buffer, 2);
|
||||
packet_buffer[4] = codec;
|
||||
|
||||
VoicePacketFlags flags{};
|
||||
auto data = pipes::buffer_view(packet_buffer, OUT_WHISPER_PKT_OFFSET + data_length);
|
||||
for(const auto& cl : available_clients){
|
||||
cl->send_voice_whisper_packet(data, flags);
|
||||
}
|
||||
|
||||
this->updateSpeak(false, system_clock::now());
|
||||
} else {
|
||||
auto clientCount = (uint8_t) data[offset++];
|
||||
auto channelCount = (uint8_t) data[offset++];
|
||||
if(data.length() < 5 + channelCount * 2 + clientCount * 8) {
|
||||
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {} Required: {}", CLIENT_STR_LOG_PREFIX, data.length(), to_string(5 + channelCount * 2 + clientCount * 8));
|
||||
auto channelCount = (uint8_t) payload[payload_offset++];
|
||||
auto clientCount = (uint8_t) payload[payload_offset++];
|
||||
if(payload.length() < 5 + clientCount * 2 + channelCount * 8) {
|
||||
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {} Required: {}", CLIENT_STR_LOG_PREFIX, payload.length(), to_string(5 + channelCount * 2 + clientCount * 8));
|
||||
return;
|
||||
}
|
||||
|
||||
this->resetIdleTime();
|
||||
ChannelId channelIds[clientCount];
|
||||
ClientId clientIds[channelCount];
|
||||
ChannelId channelIds[channelCount];
|
||||
ClientId clientIds[clientCount];
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++)
|
||||
channelIds[index] = be2le64((char*) data.data_ptr(), offset, &offset);
|
||||
for(uint8_t index = 0; index < channelCount; index++)
|
||||
clientIds[index] = be2le16((char*) data.data_ptr(), offset, &offset);
|
||||
|
||||
auto available_clients = this->server->getClients();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
if(!speakingClient || cl == this || !speakingClient->currentChannel) return true;
|
||||
|
||||
auto clientChannelId = cl->currentChannel->channelId();
|
||||
auto clientId = cl->getClientId();
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++)
|
||||
if(channelIds[index] == clientChannelId) return false;
|
||||
|
||||
for(uint8_t index = 0; index < channelCount; index++)
|
||||
if(clientIds[index] == clientId) return false;
|
||||
|
||||
return true;
|
||||
}), available_clients.end());
|
||||
|
||||
auto self_lock = this->_this.lock();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
|
||||
}), available_clients.end());
|
||||
|
||||
if(available_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
for(uint8_t index = 0; index < channelCount; index++) {
|
||||
channelIds[index] = be2le64((char*) payload.data_ptr(), payload_offset, &payload_offset);
|
||||
}
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++) {
|
||||
clientIds[index] = be2le16((char*) payload.data_ptr(), payload_offset, &payload_offset);
|
||||
}
|
||||
|
||||
size_t dataLength = data.length() - offset;
|
||||
#ifdef PKT_LOG_WHISPER
|
||||
logTrace(this->getServerId(), "{} Whisper data length: {}. Client count: {}. Channel count: {}.", CLIENT_STR_LOG_PREFIX, dataLength, clientCount, channelCount);
|
||||
#endif
|
||||
|
||||
for(const auto& client : this->server->getClients()) {
|
||||
auto speaking_client = dynamic_pointer_cast<SpeakingClient>(client);
|
||||
if(!speaking_client || client == this || !speaking_client->currentChannel)
|
||||
continue;
|
||||
|
||||
auto clientChannelId = speaking_client->getChannelId();
|
||||
auto clientId = speaking_client->getClientId();
|
||||
|
||||
for(uint8_t index = 0; index < channelCount; index++) {
|
||||
if(channelIds[index] == clientChannelId) {
|
||||
goto add_client;
|
||||
}
|
||||
}
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++) {
|
||||
if(clientIds[index] == clientId) {
|
||||
goto add_client;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
add_client:
|
||||
if(!speaking_client->shouldReceiveVoiceWhisper(this->ref())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
target_clients.push_back(speaking_client);
|
||||
}
|
||||
}
|
||||
|
||||
if(target_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(target_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* send the packet */
|
||||
{
|
||||
size_t voice_payload_length = payload.length() - payload_offset;
|
||||
|
||||
//Create the packet data
|
||||
char packetBuffer[OUT_WHISPER_PKT_OFFSET + dataLength];
|
||||
if(offset < data.length())
|
||||
memcpy(&packetBuffer[OUT_WHISPER_PKT_OFFSET], &data[offset], dataLength);
|
||||
char whisper_packet_buffer[kWhisperMaxHeaderLength + voice_payload_length];
|
||||
size_t whisper_packet_offset{0};
|
||||
size_t whisper_packet_teamspeak_offset{0};
|
||||
|
||||
le2be16(vpacketId, packetBuffer, 0);
|
||||
le2be16(this->getClientId(), packetBuffer, 2);
|
||||
packetBuffer[4] = codec;
|
||||
/* writing the teaspeak header */
|
||||
if(head) {
|
||||
auto uniqueId = this->getUid();
|
||||
auto nickname = this->getDisplayName();
|
||||
|
||||
VoicePacketFlags flags{};
|
||||
auto data = pipes::buffer_view(packetBuffer, OUT_WHISPER_PKT_OFFSET + dataLength);
|
||||
if(uniqueId.length() > kWhisperClientUniqueIdLength) {
|
||||
logCritical(LOG_GENERAL, "Clients unique id is longer than the expected max length of {}. Unique length: {}", kWhisperClientUniqueIdLength, uniqueId.length());
|
||||
return;
|
||||
}
|
||||
|
||||
for(const auto& cl : available_clients){ //Faster?
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
assert(speakingClient);
|
||||
if(speakingClient->shouldReceiveVoiceWhisper(_this.lock()))
|
||||
speakingClient->send_voice_whisper_packet(data, flags);
|
||||
if(nickname.length() > kMaxWhisperClientNameLength) {
|
||||
logCritical(LOG_GENERAL, "Clients name is longer than the expected max length of {}. Name length: {}", kMaxWhisperClientNameLength, nickname.length());
|
||||
return;
|
||||
}
|
||||
|
||||
memset(whisper_packet_buffer + whisper_packet_offset, 0, kWhisperClientUniqueIdLength);
|
||||
memcpy(whisper_packet_buffer + whisper_packet_offset, uniqueId.data(), uniqueId.length());
|
||||
whisper_packet_offset += kWhisperClientUniqueIdLength;
|
||||
|
||||
whisper_packet_buffer[whisper_packet_offset++] = nickname.length();
|
||||
memcpy(whisper_packet_buffer + whisper_packet_offset, nickname.data(), nickname.length());
|
||||
whisper_packet_offset += nickname.length();
|
||||
}
|
||||
|
||||
this->updateSpeak(false, system_clock::now());
|
||||
/* writing the "normal" header and payload */
|
||||
{
|
||||
whisper_packet_teamspeak_offset = whisper_packet_offset;
|
||||
|
||||
*(uint16_t*) &whisper_packet_buffer[whisper_packet_offset] = htons(voice_packet_id);
|
||||
whisper_packet_offset += 2;
|
||||
|
||||
*(uint16_t*) &whisper_packet_buffer[whisper_packet_offset] = htons(this->getClientId());
|
||||
whisper_packet_offset += 2;
|
||||
|
||||
whisper_packet_buffer[whisper_packet_offset++] = voice_codec;
|
||||
|
||||
if(voice_payload_length > 0) {
|
||||
memcpy(&whisper_packet_buffer[whisper_packet_offset], &payload[payload_offset], voice_payload_length);
|
||||
whisper_packet_offset += voice_payload_length;
|
||||
}
|
||||
}
|
||||
|
||||
VoicePacketFlags flags{};
|
||||
flags.head = head;
|
||||
|
||||
pipes::buffer_view teaspeak_packet{}, teamspeak_packet{};
|
||||
teaspeak_packet = pipes::buffer_view{whisper_packet_buffer, whisper_packet_offset};
|
||||
teamspeak_packet = pipes::buffer_view{whisper_packet_buffer + whisper_packet_teamspeak_offset, whisper_packet_offset - whisper_packet_teamspeak_offset};
|
||||
|
||||
auto self_ref = this->ref();
|
||||
for(const auto& cl : target_clients) {
|
||||
if(cl == self_ref || cl->shouldReceiveVoiceWhisper(self_ref)) {
|
||||
cl->send_voice_whisper_packet(teamspeak_packet, teaspeak_packet, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->resetIdleTime();
|
||||
this->updateSpeak(false, std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
#define TEST_PARM(type) \
|
||||
@@ -493,6 +532,19 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
|
||||
logWarning(this->getServerId(), "{} Tried to join within an invalid supplied '{}' ({})", CLIENT_STR_LOG_PREFIX, key,cmd[key].string());
|
||||
return command_result{error::client_hacked};
|
||||
}
|
||||
} else if(key == "client_talk_request_msg") {
|
||||
if(cmd["client_talk_request_msg"].string().length() > ts::config::server::limits::talk_power_request_message_length)
|
||||
return command_result{error::parameter_invalid_size, "client_talk_request_msg"};
|
||||
} else if(key == "client_away_message") {
|
||||
if(cmd["client_away_message"].string().length() > ts::config::server::limits::afk_message_length)
|
||||
return command_result{error::parameter_invalid_size, "client_away_message"};
|
||||
} else if(key == "client_nickname_phonetic") {
|
||||
auto name = cmd["client_nickname_phonetic"].string();
|
||||
if (count_characters(name) > 30) return command_result{error::parameter_invalid, "client_nickname_phonetic"};
|
||||
} else if(key == "client_nickname") {
|
||||
auto name = cmd["client_nickname"].string();
|
||||
if (count_characters(name) < 3) return command_result{error::parameter_invalid, "client_nickname"};
|
||||
if (count_characters(name) > 30) return command_result{error::parameter_invalid, "client_nickname"};
|
||||
}
|
||||
|
||||
const auto &info = property::find<property::ClientProperties>(key);
|
||||
@@ -800,11 +852,15 @@ void SpeakingClient::processJoin() {
|
||||
}
|
||||
}
|
||||
debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
|
||||
|
||||
serverInstance->action_logger()->client_channel_logger.log_client_join(this->getServerId(), this->ref(), this->getChannelId(), this->currentChannel->name());
|
||||
}
|
||||
|
||||
void SpeakingClient::processLeave() {
|
||||
auto ownLock = _this.lock();
|
||||
auto server = this->getServer();
|
||||
|
||||
auto channel = this->currentChannel;
|
||||
if(server){
|
||||
logMessage(this->getServerId(), "Voice client {}/{} ({}) from {} left.", this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()));
|
||||
{
|
||||
|
||||
@@ -3,98 +3,96 @@
|
||||
#include <json/json.h>
|
||||
#include "ConnectedClient.h"
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class VirtualServer;
|
||||
class SpeakingClient : public ConnectedClient {
|
||||
public:
|
||||
struct VoicePacketFlags {
|
||||
bool encrypted : 1;
|
||||
bool head : 1;
|
||||
bool fragmented : 1; /* used by MONO. IDK What this is */
|
||||
bool new_protocol : 1;
|
||||
char _unused : 4;
|
||||
namespace ts::server {
|
||||
class VirtualServer;
|
||||
class SpeakingClient : public ConnectedClient {
|
||||
public:
|
||||
struct VoicePacketFlags {
|
||||
bool encrypted : 1;
|
||||
bool head : 1;
|
||||
bool fragmented : 1; /* used by MONO. IDK What this is */
|
||||
bool new_protocol : 1;
|
||||
char _unused : 4;
|
||||
|
||||
VoicePacketFlags() : encrypted{false}, head{false}, fragmented{false}, new_protocol{false}, _unused{0} { }
|
||||
};
|
||||
static_assert(sizeof(VoicePacketFlags) == 1);
|
||||
VoicePacketFlags() : encrypted{false}, head{false}, fragmented{false}, new_protocol{false}, _unused{0} { }
|
||||
};
|
||||
static_assert(sizeof(VoicePacketFlags) == 1);
|
||||
|
||||
enum HandshakeState {
|
||||
BEGIN,
|
||||
IDENTITY_PROOF,
|
||||
SUCCEEDED
|
||||
};
|
||||
enum IdentityType : uint8_t {
|
||||
TEASPEAK_FORUM,
|
||||
TEAMSPEAK,
|
||||
NICKNAME,
|
||||
enum HandshakeState {
|
||||
BEGIN,
|
||||
IDENTITY_PROOF,
|
||||
SUCCEEDED
|
||||
};
|
||||
enum IdentityType : uint8_t {
|
||||
TEASPEAK_FORUM,
|
||||
TEAMSPEAK,
|
||||
NICKNAME,
|
||||
|
||||
UNSET = 0xff
|
||||
};
|
||||
UNSET = 0xff
|
||||
};
|
||||
|
||||
SpeakingClient(sql::SqlManager* a, const std::shared_ptr<VirtualServer>& b) : ConnectedClient(a, b) {
|
||||
speak_begin = std::chrono::system_clock::now();
|
||||
speak_last_packet = std::chrono::system_clock::now();
|
||||
};
|
||||
~SpeakingClient() override = default;
|
||||
SpeakingClient(sql::SqlManager* a, const std::shared_ptr<VirtualServer>& b) : ConnectedClient(a, b) {
|
||||
speak_begin = std::chrono::system_clock::now();
|
||||
speak_last_packet = std::chrono::system_clock::now();
|
||||
};
|
||||
~SpeakingClient() override = default;
|
||||
|
||||
//Voice
|
||||
virtual void send_voice_packet(const pipes::buffer_view& /* voice packet data */, const VoicePacketFlags& /* flags */) = 0;
|
||||
virtual bool shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender);
|
||||
//Voice
|
||||
virtual void send_voice_packet(const pipes::buffer_view& /* voice packet data */, const VoicePacketFlags& /* flags */) = 0;
|
||||
virtual bool shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender);
|
||||
|
||||
//Whisper
|
||||
bool shouldReceiveVoiceWhisper(const std::shared_ptr<ConnectedClient> &sender);
|
||||
virtual void send_voice_whisper_packet(const pipes::buffer_view& /* voice packet data */, const VoicePacketFlags& /* flags */) = 0;
|
||||
//Whisper
|
||||
bool shouldReceiveVoiceWhisper(const std::shared_ptr<ConnectedClient> &sender);
|
||||
virtual void send_voice_whisper_packet(const pipes::buffer_view& /* teamspeak payload */, const pipes::buffer_view& /* teaspeak payload */, const VoicePacketFlags& /* flags */) = 0;
|
||||
|
||||
inline std::chrono::milliseconds takeSpokenTime() {
|
||||
auto time = this->speak_time;
|
||||
this->speak_time = std::chrono::milliseconds(0);
|
||||
return time;
|
||||
}
|
||||
protected:
|
||||
void tick(const std::chrono::system_clock::time_point &time) override;
|
||||
inline std::chrono::milliseconds takeSpokenTime() {
|
||||
auto time = this->speak_time;
|
||||
this->speak_time = std::chrono::milliseconds(0);
|
||||
return time;
|
||||
}
|
||||
protected:
|
||||
void tick(const std::chrono::system_clock::time_point &time) override;
|
||||
|
||||
protected:
|
||||
public:
|
||||
void updateChannelClientProperties(bool channel_lock, bool notify) override;
|
||||
protected:
|
||||
public:
|
||||
void updateChannelClientProperties(bool channel_lock, bool notify) override;
|
||||
|
||||
protected:
|
||||
command_result handleCommand(Command &command) override;
|
||||
protected:
|
||||
command_result handleCommand(Command &command) override;
|
||||
|
||||
public:
|
||||
void handlePacketVoice(const pipes::buffer_view&, bool head, bool fragmented);
|
||||
virtual void handlePacketVoiceWhisper(const pipes::buffer_view&, bool /* new */);
|
||||
public:
|
||||
void handlePacketVoice(const pipes::buffer_view&, bool head, bool fragmented);
|
||||
virtual void handlePacketVoiceWhisper(const pipes::buffer_view&, bool /* new */, bool /* head */);
|
||||
|
||||
void processJoin();
|
||||
void processLeave();
|
||||
void processJoin();
|
||||
void processLeave();
|
||||
|
||||
virtual command_result handleCommandHandshakeBegin(Command&);
|
||||
virtual command_result handleCommandHandshakeIdentityProof(Command &);
|
||||
virtual command_result handleCommandClientInit(Command&);
|
||||
virtual command_result handleCommandHandshakeBegin(Command&);
|
||||
virtual command_result handleCommandHandshakeIdentityProof(Command &);
|
||||
virtual command_result handleCommandClientInit(Command&);
|
||||
|
||||
void triggerVoiceEnd();
|
||||
inline void updateSpeak(bool onlyUpdate, const std::chrono::system_clock::time_point &time);
|
||||
std::chrono::milliseconds speak_accuracy = std::chrono::seconds{1};
|
||||
void triggerVoiceEnd();
|
||||
inline void updateSpeak(bool onlyUpdate, const std::chrono::system_clock::time_point &time);
|
||||
std::chrono::milliseconds speak_accuracy = std::chrono::seconds{1};
|
||||
|
||||
threads::Mutex speak_lock;
|
||||
std::chrono::milliseconds speak_time = std::chrono::milliseconds{0};
|
||||
std::chrono::system_clock::time_point speak_begin;
|
||||
std::chrono::system_clock::time_point speak_last_packet;
|
||||
threads::Mutex speak_lock;
|
||||
std::chrono::milliseconds speak_time = std::chrono::milliseconds{0};
|
||||
std::chrono::system_clock::time_point speak_begin;
|
||||
std::chrono::system_clock::time_point speak_last_packet;
|
||||
|
||||
std::chrono::system_clock::time_point speak_last_no_whisper_target;
|
||||
std::chrono::system_clock::time_point speak_last_too_many_whisper_targets;
|
||||
std::chrono::system_clock::time_point speak_last_no_whisper_target;
|
||||
std::chrono::system_clock::time_point speak_last_too_many_whisper_targets;
|
||||
|
||||
permission::v2::PermissionFlaggedValue max_idle_time{permission::v2::empty_permission_flagged_value};
|
||||
struct {
|
||||
HandshakeState state{HandshakeState::BEGIN};
|
||||
permission::v2::PermissionFlaggedValue max_idle_time{permission::v2::empty_permission_flagged_value};
|
||||
struct {
|
||||
HandshakeState state{HandshakeState::BEGIN};
|
||||
|
||||
IdentityType identityType{IdentityType::UNSET};
|
||||
std::string proof_message;
|
||||
//TeamSpeak
|
||||
std::shared_ptr<ecc_key> identityKey;
|
||||
//TeaSpeak
|
||||
std::shared_ptr<Json::Value> identityData;
|
||||
} handshake;
|
||||
};
|
||||
}
|
||||
IdentityType identityType{IdentityType::UNSET};
|
||||
std::string proof_message;
|
||||
//TeamSpeak
|
||||
std::shared_ptr<ecc_key> identityKey;
|
||||
//TeaSpeak
|
||||
std::shared_ptr<Json::Value> identityData;
|
||||
} handshake;
|
||||
};
|
||||
}
|
||||
@@ -35,7 +35,7 @@ command_result SpeakingClient::handleCommandHandshakeBegin(Command& cmd) { //If
|
||||
if(authenticationMethod == IdentityType::TEAMSPEAK) {
|
||||
this->handshake.identityType = IdentityType::TEAMSPEAK;
|
||||
|
||||
auto identity = base64::decode(cmd["publicKey"]);
|
||||
auto identity = base64::decode(cmd["publicKey"].string());
|
||||
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd["publicKey"].string()));
|
||||
|
||||
this->handshake.identityKey = shared_ptr<ecc_key>(new ecc_key{}, free_ecc);
|
||||
@@ -144,7 +144,7 @@ command_result SpeakingClient::handleCommandHandshakeIdentityProof(Command& cmd)
|
||||
this->properties()[property::CLIENT_TEAFORO_NAME] = (*this->handshake.identityData)["user_name"].asString();
|
||||
this->handshake.state = HandshakeState::SUCCEEDED;
|
||||
} else if(this->handshake.identityType == IdentityType::TEAMSPEAK) {
|
||||
auto proof = base64::decode(cmd["proof"]);
|
||||
auto proof = base64::decode(cmd["proof"].string());
|
||||
|
||||
int result;
|
||||
if(ecc_verify_hash((u_char*) proof.data(), proof.length(), (u_char*) this->handshake.proof_message.data(), this->handshake.proof_message.length(), &result, this->handshake.identityKey.get()) != CRYPT_OK)
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include <PermissionManager.h>
|
||||
#include <src/client/ConnectedClient.h>
|
||||
#include "./helpers.h"
|
||||
#include "../../manager/ActionLogger.h"
|
||||
|
||||
namespace ts::command::bulk_parser {
|
||||
template <bool kParseValue>
|
||||
class PermissionBulkParser {
|
||||
friend class PermissionBulkParser<false>;
|
||||
public:
|
||||
explicit PermissionBulkParser(ts::ParameterBulk& bulk) {
|
||||
if(bulk.has("permid")) {
|
||||
@@ -45,7 +47,7 @@ namespace ts::command::bulk_parser {
|
||||
}
|
||||
}
|
||||
PermissionBulkParser(const PermissionBulkParser&) = delete;
|
||||
PermissionBulkParser(PermissionBulkParser&&) = default;
|
||||
PermissionBulkParser(PermissionBulkParser&&) noexcept = default;
|
||||
|
||||
~PermissionBulkParser() {
|
||||
this->error_.release_data();
|
||||
@@ -78,9 +80,9 @@ namespace ts::command::bulk_parser {
|
||||
|
||||
inline void apply_to(const std::shared_ptr<permission::v2::PermissionManager>& manager, permission::v2::PermissionUpdateType mode) const {
|
||||
if(this->is_grant_permission()) {
|
||||
manager->set_permission(this->permission_type(), { 0, this->value() }, permission::v2::PermissionUpdateType::do_nothing, mode);
|
||||
this->old_value = manager->set_permission(this->permission_type(), { 0, this->value() }, permission::v2::PermissionUpdateType::do_nothing, mode);
|
||||
} else {
|
||||
manager->set_permission(
|
||||
this->old_value = manager->set_permission(
|
||||
this->permission_type(),
|
||||
{ this->value(), 0 },
|
||||
mode,
|
||||
@@ -91,11 +93,61 @@ namespace ts::command::bulk_parser {
|
||||
}
|
||||
}
|
||||
|
||||
inline void log_update(
|
||||
server::log::PermissionActionLogger& logger,
|
||||
ServerId sid,
|
||||
const std::shared_ptr<server::ConnectedClient>& issuer,
|
||||
server::log::PermissionTarget target,
|
||||
permission::v2::PermissionUpdateType mode,
|
||||
uint64_t id1, const std::string& id1_name,
|
||||
uint64_t id2, const std::string& id2_name
|
||||
) const {
|
||||
if(this->is_grant_permission()) {
|
||||
switch (mode) {
|
||||
case permission::v2::delete_value:
|
||||
if(!this->old_value.flags.grant_set)
|
||||
return;
|
||||
|
||||
logger.log_permission_remove_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.grant);
|
||||
break;
|
||||
|
||||
case permission::v2::set_value:
|
||||
if(this->old_value.flags.grant_set) {
|
||||
logger.log_permission_edit_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.grant, this->value_);
|
||||
} else {
|
||||
logger.log_permission_add_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->value_);
|
||||
}
|
||||
break;
|
||||
case permission::v2::do_nothing:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (mode) {
|
||||
case permission::v2::delete_value:
|
||||
if(!this->old_value.flags.value_set)
|
||||
return;
|
||||
|
||||
logger.log_permission_remove_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip);
|
||||
break;
|
||||
|
||||
case permission::v2::set_value:
|
||||
if(this->old_value.flags.value_set) {
|
||||
logger.log_permission_edit_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip, this->value_, this->flag_negated_, this->flag_skip_);
|
||||
} else {
|
||||
logger.log_permission_add_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip);
|
||||
}
|
||||
break;
|
||||
case permission::v2::do_nothing:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void apply_to_channel(const std::shared_ptr<permission::v2::PermissionManager>& manager, permission::v2::PermissionUpdateType mode, ChannelId channel_id) const {
|
||||
if(this->is_grant_permission()) {
|
||||
manager->set_channel_permission(this->permission_type(), channel_id, { this->value(), true }, permission::v2::PermissionUpdateType::do_nothing, mode);
|
||||
this->old_value = manager->set_channel_permission(this->permission_type(), channel_id, { this->value(), true }, permission::v2::PermissionUpdateType::do_nothing, mode);
|
||||
} else {
|
||||
manager->set_channel_permission(
|
||||
this->old_value = manager->set_channel_permission(
|
||||
this->permission_type(),
|
||||
channel_id,
|
||||
{ this->value(), true },
|
||||
@@ -122,6 +174,7 @@ namespace ts::command::bulk_parser {
|
||||
permission::PermissionValue value_{0};
|
||||
ts::command_result error_{error::ok};
|
||||
|
||||
mutable permission::v2::PermissionContainer old_value{};
|
||||
#ifndef NDEBUG
|
||||
bool error_released_{false};
|
||||
#endif
|
||||
@@ -131,7 +184,7 @@ namespace ts::command::bulk_parser {
|
||||
class PermissionBulksParser {
|
||||
public:
|
||||
PermissionBulksParser(const PermissionBulksParser&) = delete;
|
||||
PermissionBulksParser(PermissionBulksParser&&) = default;
|
||||
PermissionBulksParser(PermissionBulksParser&&) noexcept = default;
|
||||
|
||||
template <typename base_iterator>
|
||||
struct FilteredPermissionIterator : public base_iterator {
|
||||
|
||||
@@ -5,18 +5,17 @@
|
||||
#include "../../build.h"
|
||||
#include "../ConnectedClient.h"
|
||||
#include "../InternalClient.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "../../server/VoiceServer.h"
|
||||
#include "../voice/VoiceClient.h"
|
||||
#include "PermissionManager.h"
|
||||
#include "../../InstanceHandler.h"
|
||||
#include "../../server/QueryServer.h"
|
||||
#include "../file/FileClient.h"
|
||||
#include "../music/MusicClient.h"
|
||||
#include "../query/QueryClient.h"
|
||||
#include "../../weblist/WebListManager.h"
|
||||
#include "../../manager/ConversationManager.h"
|
||||
#include "../../manager/PermissionNameMapper.h"
|
||||
#include "../../manager/ActionLogger.h"
|
||||
#include <cstdint>
|
||||
|
||||
#include "helpers.h"
|
||||
@@ -140,17 +139,53 @@ command_result ConnectedClient::handleCommandChannelGroupAdd(Command &cmd) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_create, 1);
|
||||
|
||||
auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get();
|
||||
if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_NORMAL && !this->server) return command_result{error::parameter_invalid, "You cant create normal channel groups on the template server"};
|
||||
if(cmd["name"].string().empty()) return command_result{error::parameter_invalid, "invalid group name"};
|
||||
|
||||
log::GroupType log_group_type;
|
||||
if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_QUERY) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
} else if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_TEMPLATE) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
} else {
|
||||
if(!this->server)
|
||||
return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"};
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
}
|
||||
|
||||
if(cmd["name"].string().empty())
|
||||
return command_result{error::parameter_invalid, "invalid group name"};
|
||||
|
||||
for(const auto& gr : group_manager->availableServerGroups(true))
|
||||
if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "Group already exists"};
|
||||
if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_CHANNEL)
|
||||
return command_result{error::parameter_invalid, "Group already exists"};
|
||||
|
||||
auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_CHANNEL, cmd["type"].as<GroupType>(), cmd["name"].string());
|
||||
serverInstance->action_logger()->group_logger.log_group_create(this->getServerId(), this->ref(), log::GroupTarget::CHANNEL, log_group_type, group->groupId(), group->name(), 0, "");
|
||||
|
||||
{
|
||||
ts::command_builder notify{this->notify_response_command("notifychannelgroupadded")};
|
||||
notify.put_unchecked(0, "cgid", group->groupId());
|
||||
this->sendCommand(notify);
|
||||
}
|
||||
|
||||
if (group) {
|
||||
group->permissions()->set_permission(permission::b_group_is_permanent, {1, 0}, permission::v2::set_value, permission::v2::do_nothing);
|
||||
if(this->server)
|
||||
this->server->forEachClient([](shared_ptr<ConnectedClient> cl) {
|
||||
cl->notifyChannelGroupList();
|
||||
});
|
||||
if(this->server) {
|
||||
if(this->getType() == ClientType::CLIENT_QUERY) {
|
||||
this->server->forEachClient([&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
if(cl == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
cl->notifyChannelGroupList();
|
||||
});
|
||||
} else {
|
||||
this->server->forEachClient([](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
cl->notifyChannelGroupList();
|
||||
});
|
||||
}
|
||||
}
|
||||
} else return command_result{error::group_invalid_id};
|
||||
return command_result{error::ok};
|
||||
}
|
||||
@@ -182,6 +217,7 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) {
|
||||
return permission::b_serverinstance_modify_querygroup;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_NORMAL:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -213,6 +249,28 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) {
|
||||
if(!group_manager->copyGroupPermissions(source_group, target_group))
|
||||
return command_result{error::vs_critical, "failed to copy group permissions"};
|
||||
|
||||
log::GroupType log_group_type;
|
||||
switch (target_group->type()) {
|
||||
case GroupType::GROUP_TYPE_QUERY:
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_TEMPLATE:
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_NORMAL:
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
break;
|
||||
|
||||
default:
|
||||
return command_result{error::parameter_invalid, "type"};
|
||||
}
|
||||
|
||||
serverInstance->action_logger()->group_logger.log_group_permission_copy(target_group->type() != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(),
|
||||
this->ref(), log::GroupTarget::CHANNEL, log_group_type, target_group->groupId(), target_group->name(), source_group->groupId(), source_group->name());
|
||||
|
||||
|
||||
global_update = !this->server || !group_manager->isLocalGroup(target_group);
|
||||
} else {
|
||||
//Copy a new group
|
||||
@@ -224,6 +282,24 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) {
|
||||
return command_result{result};
|
||||
}
|
||||
|
||||
log::GroupType log_group_type;
|
||||
switch (target_type) {
|
||||
case GroupType::GROUP_TYPE_QUERY:
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_TEMPLATE:
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_NORMAL:
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
break;
|
||||
|
||||
default:
|
||||
return command_result{error::parameter_invalid, "type"};
|
||||
}
|
||||
|
||||
if(!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL)
|
||||
return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"};
|
||||
|
||||
@@ -233,6 +309,8 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) {
|
||||
auto target_group_id = group_manager->copyGroup(source_group, target_type, cmd["name"], target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId());
|
||||
if(target_group_id == 0)
|
||||
return command_result{error::vs_critical, "failed to copy group"};
|
||||
serverInstance->action_logger()->group_logger.log_group_create(target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(),
|
||||
this->ref(), log::GroupTarget::CHANNEL, log_group_type, target_group_id, cmd["name"], source_group->groupId(), source_group->name());
|
||||
|
||||
if(this->getType() == ClientType::CLIENT_QUERY) {
|
||||
Command notify("");
|
||||
@@ -257,10 +335,26 @@ command_result ConnectedClient::handleCommandChannelGroupRename(Command &cmd) {
|
||||
|
||||
auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get();
|
||||
auto channel_group = group_manager->findGroup(cmd["cgid"].as<GroupId>());
|
||||
if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
|
||||
if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL)
|
||||
return command_result{error::parameter_invalid, "invalid channel group id"};
|
||||
ACTION_REQUIRES_GROUP_PERMISSION(channel_group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
|
||||
|
||||
auto type = channel_group->type();
|
||||
log::GroupType log_group_type;
|
||||
if(type == GroupType::GROUP_TYPE_QUERY) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
} else {
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
}
|
||||
|
||||
auto old_name = channel_group->name();
|
||||
group_manager->renameGroup(channel_group, cmd["name"].string());
|
||||
serverInstance->action_logger()->group_logger.log_group_rename(this->getServerId(), this->ref(), log::GroupTarget::CHANNEL, log_group_type, channel_group->groupId(), channel_group->name(), old_name);
|
||||
|
||||
if(this->server)
|
||||
this->server->forEachClient([](shared_ptr<ConnectedClient> cl) {
|
||||
cl->notifyChannelGroupList();
|
||||
@@ -286,21 +380,37 @@ command_result ConnectedClient::handleCommandChannelGroupDel(Command &cmd) {
|
||||
}
|
||||
if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP] == channel_group->groupId())
|
||||
return command_result{error::parameter_invalid, "Could not delete instance default channel group!"};
|
||||
|
||||
if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP] == channel_group->groupId())
|
||||
return command_result{error::parameter_invalid, "Could not delete instance default channel admin group!"};
|
||||
|
||||
auto type = channel_group->type();
|
||||
log::GroupType log_group_type;
|
||||
if(type == GroupType::GROUP_TYPE_QUERY) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
} else {
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
}
|
||||
|
||||
if (!cmd["force"].as<bool>())
|
||||
if (!group_manager->listGroupMembers(channel_group, false).empty())
|
||||
return command_result{error::database_empty_result, "group not empty!"};
|
||||
|
||||
if (group_manager->deleteGroup(channel_group) && this->server) {
|
||||
this->server->forEachClient([&](shared_ptr<ConnectedClient> cl) {
|
||||
if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) {
|
||||
if(cl->update_cached_permissions()) /* update cached calculated permissions */
|
||||
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
||||
}
|
||||
cl->notifyChannelGroupList();
|
||||
});
|
||||
if (group_manager->deleteGroup(channel_group)) {
|
||||
serverInstance->action_logger()->group_logger.log_group_delete(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, channel_group->groupId(), channel_group->name());
|
||||
if(this->server) {
|
||||
this->server->forEachClient([&](shared_ptr<ConnectedClient> cl) {
|
||||
if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) {
|
||||
if(cl->update_cached_permissions()) /* update cached calculated permissions */
|
||||
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
||||
}
|
||||
cl->notifyChannelGroupList();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
@@ -415,6 +525,15 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) {
|
||||
bool updateList{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::CHANNEL_GROUP,
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
0, "",
|
||||
channelGroup->groupId(), channelGroup->name()
|
||||
);
|
||||
|
||||
updateList |= ppermission.is_group_property();
|
||||
}
|
||||
|
||||
@@ -454,6 +573,14 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) {
|
||||
bool updateList{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::CHANNEL_GROUP,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
0, "",
|
||||
channelGroup->groupId(), channelGroup->name()
|
||||
);
|
||||
updateList |= ppermission.is_group_property();
|
||||
}
|
||||
|
||||
@@ -647,7 +774,17 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
|
||||
}
|
||||
|
||||
if (cmd["channel_name"].string().length() < 1) return command_result{error::channel_name_inuse, "Invalid channel name"};
|
||||
if (cmd["channel_name"].string().length() < 1) {
|
||||
return command_result{error::channel_name_invalid};
|
||||
}
|
||||
|
||||
if (cmd[0].has("channel_name") && count_characters(cmd["channel_name"]) > 40) {
|
||||
return command_result{error::channel_name_invalid};
|
||||
}
|
||||
|
||||
if (count_characters(cmd["channel_name_phonetic"]) > 40) {
|
||||
return command_result{error::channel_name_invalid};
|
||||
}
|
||||
|
||||
{
|
||||
if (target_tree->findChannel(cmd["channel_name"].as<std::string>(), parent ? dynamic_pointer_cast<BasicChannel>(parent->entry) : nullptr)) return command_result{error::channel_name_inuse, "Name already in use"};
|
||||
@@ -686,24 +823,50 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
);
|
||||
}
|
||||
|
||||
for (auto &prop : cmd[0].keys()) {
|
||||
if (prop == "channel_flag_default") continue;
|
||||
if (prop == "channel_order") continue;
|
||||
if (prop == "channel_name") continue;
|
||||
if (prop == "cpid") continue;
|
||||
if (prop == "cid") continue;
|
||||
/* log channel create */
|
||||
{
|
||||
log::ChannelType log_channel_type;
|
||||
switch (created_channel->channelType()) {
|
||||
case ChannelType::permanent:
|
||||
log_channel_type = log::ChannelType::PERMANENT;
|
||||
break;
|
||||
|
||||
const auto &property = property::find<property::ChannelProperties>(prop);
|
||||
case ChannelType::semipermanent:
|
||||
log_channel_type = log::ChannelType::SEMI_PERMANENT;
|
||||
break;
|
||||
|
||||
case ChannelType::temporary:
|
||||
log_channel_type = log::ChannelType::TEMPORARY;
|
||||
break;
|
||||
}
|
||||
|
||||
serverInstance->action_logger()->channel_logger.log_channel_create(this->getServerId(), this->ref(), created_channel->channelId(), log_channel_type);
|
||||
}
|
||||
|
||||
for (auto &property_name : cmd[0].keys()) {
|
||||
if (property_name == "channel_flag_default") continue;
|
||||
if (property_name == "channel_order") continue;
|
||||
if (property_name == "channel_name") continue;
|
||||
if (property_name == "cpid") continue;
|
||||
if (property_name == "cid") continue;
|
||||
|
||||
const auto &property = property::find<property::ChannelProperties>(property_name);
|
||||
if(property == property::CHANNEL_UNDEFINED) {
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + prop);
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + property_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!property.validate_input(cmd[prop].as<string>())) {
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as<string>() + "', Property: '" + std::string{property.name} + "')");
|
||||
if(!property.validate_input(cmd[property_name].as<string>())) {
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[property_name].as<string>() + "', Property: '" + std::string{property.name} + "')");
|
||||
continue;
|
||||
}
|
||||
created_channel->properties()[property] = cmd[prop].as<std::string>();
|
||||
auto prop = created_channel->properties()[property];
|
||||
auto old_value = prop.value();
|
||||
auto new_value = cmd[property_name].as<std::string>();
|
||||
if(old_value == new_value)
|
||||
continue;
|
||||
prop = new_value;
|
||||
serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), created_channel->channelId(), property, old_value, new_value);
|
||||
}
|
||||
if(created_channel->parent()) {
|
||||
if(created_channel->parent()->channelType() > created_channel->channelType())
|
||||
@@ -739,6 +902,8 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
logError(this->getServerId(), "Missing server's default channel admin group! Using default channel group!");
|
||||
channel_admin_group = this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
|
||||
}
|
||||
|
||||
/* FIXME: Log group assignment */
|
||||
this->server->groups->setChannelGroup(this->getClientDatabaseId(), channel_admin_group, created_channel);
|
||||
|
||||
if (created_channel->channelType() == ChannelType::temporary && (this->getType() == ClientType::CLIENT_TEAMSPEAK || this->getType() == ClientType::CLIENT_WEB))
|
||||
@@ -777,11 +942,15 @@ command_result ConnectedClient::handleCommandChannelDelete(Command &cmd) {
|
||||
if(!clients.empty())
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_delete_flag_force, 1, channel->channelId());
|
||||
|
||||
this->server->delete_channel(channel, this->ref(), "channel deleted", channel_tree_write_lock);
|
||||
this->server->delete_channel(channel, this->ref(), "channel deleted", channel_tree_write_lock, false);
|
||||
} else {
|
||||
auto channel_ids = channel_tree->deleteChannelRoot(channel);
|
||||
this->notifyChannelDeleted(channel_ids, this->ref());
|
||||
auto deleted_channel_ids = channel_tree->deleteChannelRoot(channel);
|
||||
for(const auto& channelId : deleted_channel_ids) {
|
||||
serverInstance->action_logger()->channel_logger.log_channel_delete(0, this->ref(), channelId, channel->channelId() == channelId ? log::ChannelDeleteReason::USER_ACTION : log::ChannelDeleteReason::PARENT_DELETED);
|
||||
}
|
||||
this->notifyChannelDeleted(deleted_channel_ids, this->ref());
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -854,13 +1023,18 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
require_write_lock = true;
|
||||
} else if (key == "channel_name") {
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_name, 1, channel_id);
|
||||
if (count_characters(cmd["channel_name"]) < 1)
|
||||
return command_result{error::channel_name_invalid};
|
||||
if (count_characters(cmd["channel_name"]) > 40)
|
||||
return command_result{error::channel_name_invalid};
|
||||
if (count_characters(cmd["channel_name"]) < 1) {
|
||||
return command_result{error::channel_name_invalid};
|
||||
}
|
||||
if (count_characters(cmd["channel_name"]) > 40) {
|
||||
return command_result{error::channel_name_invalid};
|
||||
}
|
||||
require_write_lock = true;
|
||||
update_name = true;
|
||||
} else if (key == "channel_name_phonetic") {
|
||||
if (count_characters(cmd["channel_name_phonetic"]) > 40) {
|
||||
return command_result{error::parameter_invalid, "channel_name_phonetic"};
|
||||
}
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_name, 1, channel_id);
|
||||
} else if (key == "channel_topic") {
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_topic, 1, channel_id);
|
||||
@@ -919,7 +1093,31 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
ACTION_REQUIRES_PERMISSION(permission::i_channel_create_modify_conversation_history_length, 1, channel_id);
|
||||
}
|
||||
} else if (key == "channel_flag_conversation_private") {
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_private, 1, channel_id);
|
||||
auto value = cmd["channel_flag_conversation_private"].as<bool>();
|
||||
if(value) {
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_private, 1, channel_id);
|
||||
cmd[property::name(property::CHANNEL_CONVERSATION_MODE)] = CHANNELCONVERSATIONMODE_PRIVATE;
|
||||
} else {
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_public, 1, channel_id);
|
||||
cmd[property::name(property::CHANNEL_CONVERSATION_MODE)] = CHANNELCONVERSATIONMODE_PUBLIC;
|
||||
}
|
||||
keys.push_back(&property::describe(property::CHANNEL_CONVERSATION_MODE));
|
||||
continue;
|
||||
} else if (key == "channel_conversation_mode") {
|
||||
auto value = cmd["channel_conversation_mode"].as<ChannelConversationMode>();
|
||||
switch (value) {
|
||||
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE:
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_private, 1, channel_id);
|
||||
break;
|
||||
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PUBLIC:
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_public, 1, channel_id);
|
||||
break;
|
||||
case ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE:
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_none, 1, channel_id);
|
||||
break;
|
||||
default:
|
||||
return command_result{error::parameter_invalid, "channel_conversation_mode"};
|
||||
}
|
||||
} else {
|
||||
logCritical(
|
||||
this->getServerId(),
|
||||
@@ -930,6 +1128,8 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
}
|
||||
keys.push_back(&property);
|
||||
}
|
||||
if(keys.empty())
|
||||
return command_result{error::ok};
|
||||
|
||||
unique_lock server_channel_w_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock(), defer_lock);
|
||||
if(require_write_lock) {
|
||||
@@ -1109,9 +1309,15 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
for(const property::PropertyDescription* key : keys) {
|
||||
if(*key == property::CHANNEL_ORDER) {
|
||||
/* TODO: May move that up because if it fails may some other props have already be applied */
|
||||
auto old_channel_order = channel->channelOrder();
|
||||
if (!channel_tree->change_order(channel, cmd[std::string{key->name}]))
|
||||
return command_result{error::channel_invalid_order, "Can't change order id"};
|
||||
|
||||
auto channel_parent = channel->parent() ? channel->parent()->channelId() : 0;
|
||||
serverInstance->action_logger()->channel_logger.log_channel_move(this->getServerId(), this->ref(), channel->channelId(),
|
||||
channel_parent, channel_parent,
|
||||
old_channel_order, channel->channelOrder());
|
||||
|
||||
if(this->server) {
|
||||
auto parent = channel->hasParent() ? channel_tree->findLinkedChannel(channel->parent()->channelId()) : nullptr;
|
||||
auto previous = channel_tree->findLinkedChannel(channel->previousChannelId());
|
||||
@@ -1145,6 +1351,9 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* property has already been updated as well the log has also been written */
|
||||
continue;
|
||||
} else if(*key == property::CHANNEL_FLAG_DEFAULT) {
|
||||
old_default_channel = channel_tree->getDefaultChannel();
|
||||
if(old_default_channel == channel) {
|
||||
@@ -1217,7 +1426,14 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as<int>(), 0}, permission::v2::set_value, permission::v2::do_nothing);
|
||||
}
|
||||
|
||||
channel->properties()[*key] = cmd[std::string{key->name}].string();
|
||||
auto prop = channel->properties()[*key];
|
||||
auto old_value = prop.value();
|
||||
auto new_value = cmd[std::string{key->name}].string();
|
||||
if(old_value == new_value)
|
||||
continue;
|
||||
|
||||
prop = new_value;
|
||||
serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), channel->channelId(), *key, old_value, new_value);
|
||||
}
|
||||
if(this->server) {
|
||||
vector<property::ChannelProperties> key_vector;
|
||||
@@ -1284,9 +1500,16 @@ command_result ConnectedClient::handleCommandChannelMove(Command &cmd) {
|
||||
if(channel->parent() == parent && channel->channelOrder() == (order ? order->channelId() : 0))
|
||||
return command_result{error::ok};
|
||||
|
||||
if (channel->parent() != parent)
|
||||
auto old_parent_channel_id = channel->parent() ? channel->parent()->channelId() : 0;
|
||||
auto old_channel_order = channel->channelOrder();
|
||||
|
||||
bool change_parent{channel->parent() != parent};
|
||||
bool change_order{(order ? order->channelId() : 0) != channel->channelOrder()};
|
||||
|
||||
if (change_parent)
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_parent, 1, channel_id);
|
||||
if ((order ? order->channelId() : 0) != channel->channelOrder())
|
||||
|
||||
if (change_order)
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_sortorder, 1, channel_id);
|
||||
|
||||
{
|
||||
@@ -1332,6 +1555,24 @@ command_result ConnectedClient::handleCommandChannelMove(Command &cmd) {
|
||||
} while ((current_channel = dynamic_pointer_cast<ServerChannel>(current_channel->parent())));
|
||||
}
|
||||
|
||||
/* log all the updates */
|
||||
serverInstance->action_logger()->channel_logger.log_channel_move(this->getServerId(), this->ref(), channel->channelId(),
|
||||
old_parent_channel_id, channel->parent() ? channel->parent()->channelId() : 0,
|
||||
old_channel_order, channel->channelOrder());
|
||||
|
||||
for(const auto& type_update : channel_type_updates) {
|
||||
serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), type_update->channelId(),
|
||||
property::describe(property::CHANNEL_FLAG_PERMANENT),
|
||||
"",
|
||||
type_update->properties()[property::CHANNEL_FLAG_PERMANENT].value()
|
||||
);
|
||||
serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), type_update->channelId(),
|
||||
property::describe(property::CHANNEL_FLAG_SEMI_PERMANENT),
|
||||
"",
|
||||
type_update->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].value()
|
||||
);
|
||||
}
|
||||
|
||||
if(this->server) {
|
||||
auto self_rev = this->ref();
|
||||
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
|
||||
@@ -1443,8 +1684,17 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) {
|
||||
|
||||
auto permission_manager = channel->permissions();
|
||||
auto updateClients = false, update_join_permissions = false, update_channel_properties = false;
|
||||
auto channelId = channel->channelId();
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::set_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::CHANNEL,
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
channelId, channel->name(),
|
||||
0, ""
|
||||
);
|
||||
|
||||
updateClients |= ppermission.is_client_view_property();
|
||||
update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power;
|
||||
@@ -1481,8 +1731,17 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) {
|
||||
|
||||
auto permission_manager = channel->permissions();
|
||||
auto updateClients = false, update_join_permissions = false, update_channel_properties = false;
|
||||
auto channelId = channel->channelId();
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::delete_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::CHANNEL,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
channelId, channel->name(),
|
||||
0, ""
|
||||
);
|
||||
|
||||
updateClients |= ppermission.is_client_view_property();
|
||||
update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power;
|
||||
@@ -1587,6 +1846,14 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
|
||||
bool update_view{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::delete_value, channel->channelId());
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::CLIENT_CHANNEL,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
cldbid, "",
|
||||
channel->channelId(), channel->name()
|
||||
);
|
||||
update_view |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
@@ -1647,6 +1914,14 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
|
||||
bool update_view{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::set_value, channel->channelId());
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::CLIENT_CHANNEL,
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
cldbid, "",
|
||||
channel->channelId(), channel->name()
|
||||
);
|
||||
update_view |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,16 @@
|
||||
#include "../../build.h"
|
||||
#include "../ConnectedClient.h"
|
||||
#include "../InternalClient.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "../voice/VoiceClient.h"
|
||||
#include "PermissionManager.h"
|
||||
#include "../../InstanceHandler.h"
|
||||
#include "../../server/QueryServer.h"
|
||||
#include "../file/FileClient.h"
|
||||
#include "../music/MusicClient.h"
|
||||
#include "../query/QueryClient.h"
|
||||
#include "../../weblist/WebListManager.h"
|
||||
#include "../../manager/ConversationManager.h"
|
||||
#include "../../manager/PermissionNameMapper.h"
|
||||
#include "../../manager/ActionLogger.h"
|
||||
#include <cstdint>
|
||||
|
||||
#include "helpers.h"
|
||||
@@ -71,9 +70,6 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) {
|
||||
|
||||
auto type = cmd["reasonid"].as<ViewReasonId>();
|
||||
auto target_channel = type == ViewReasonId::VREASON_CHANNEL_KICK ? this->server->channelTree->getDefaultChannel() : nullptr;
|
||||
auto kick_power = type == ViewReasonId::VREASON_CHANNEL_KICK ?
|
||||
this->calculate_permission(permission::i_client_kick_from_channel_power, target_channel->channelId()) :
|
||||
this->calculate_permission(permission::i_client_kick_from_server_power, 0);
|
||||
|
||||
for(size_t index = 0; index < cmd.bulkCount(); index++) {
|
||||
ConnectedLockedClient<ConnectedClient> client{this->server->find_client_by_id(cmd[index]["clid"].as<ClientId>())};
|
||||
@@ -82,17 +78,20 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) {
|
||||
result.emplace_result(error::client_invalid_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (client->getType() == CLIENT_MUSIC) {
|
||||
result.emplace_result(error::client_invalid_type);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(type == ViewReasonId::VREASON_CHANNEL_KICK) {
|
||||
auto kick_power = this->calculate_permission(permission::i_client_kick_from_channel_power, client->getChannelId());
|
||||
if(!permission::v2::permission_granted(client->calculate_permission(permission::i_client_needed_kick_from_channel_power, client->getChannelId()), kick_power)) {
|
||||
result.emplace_result(permission::i_client_needed_kick_from_channel_power);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
auto kick_power = this->calculate_permission(permission::i_client_kick_from_server_power, client->getChannelId());
|
||||
if(!permission::v2::permission_granted(client->calculate_permission(permission::i_client_needed_kick_from_server_power, client->getChannelId()), kick_power)) {
|
||||
result.emplace_result(permission::i_client_needed_kick_from_server_power);
|
||||
continue;
|
||||
@@ -104,11 +103,16 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) {
|
||||
}
|
||||
|
||||
for(auto& client : clients) {
|
||||
auto old_channel = client->getChannel();
|
||||
if(!old_channel) continue;
|
||||
|
||||
if (target_channel) {
|
||||
this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as<std::string>(), target_channel);
|
||||
serverInstance->action_logger()->client_channel_logger.log_client_kick(this->getServerId(), this->ref(), client->ref(), target_channel->channelId(), target_channel->name(), old_channel->channelId(), old_channel->name());
|
||||
} else {
|
||||
this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as<std::string>(), nullptr);
|
||||
client->close_connection(system_clock::now() + seconds(1));
|
||||
serverInstance->action_logger()->client_channel_logger.log_client_kick(this->getServerId(), this->ref(), client->ref(), 0, "", old_channel->channelId(), old_channel->name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,6 +264,9 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
|
||||
true,
|
||||
server_channel_w_lock
|
||||
);
|
||||
|
||||
serverInstance->action_logger()->client_channel_logger.log_client_move(this->getServerId(), this->ref(), client->ref(), channel->channelId(), channel->name(), oldChannel->channelId(), oldChannel->name());
|
||||
|
||||
if(std::find_if(channels.begin(), channels.end(), [&](const std::shared_ptr<BasicChannel>& channel) { return &*channel == &*oldChannel; }) == channels.end())
|
||||
channels.push_back(oldChannel);
|
||||
}
|
||||
@@ -269,7 +276,7 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
|
||||
server_channel_w_lock.lock();
|
||||
if(oldChannel->channelType() == ChannelType::temporary && oldChannel->properties()[property::CHANNEL_DELETE_DELAY].as<int64_t>() == 0)
|
||||
if(this->server->getClientsByChannelRoot(oldChannel, false).empty())
|
||||
this->server->delete_channel(dynamic_pointer_cast<ServerChannel>(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock);
|
||||
this->server->delete_channel(dynamic_pointer_cast<ServerChannel>(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock, true);
|
||||
if(server_channel_w_lock.owns_lock())
|
||||
server_channel_w_lock.unlock();
|
||||
}
|
||||
@@ -316,8 +323,12 @@ command_result ConnectedClient::handleCommandClientPoke(Command &cmd) {
|
||||
return command_result{permission::i_client_poke_max_clients};
|
||||
}
|
||||
|
||||
auto message = cmd["msg"].string();
|
||||
if(count_characters(message) > ts::config::server::limits::poke_message_length)
|
||||
return command_result{error::parameter_invalid_size, "msg"};
|
||||
|
||||
for(auto& client : clients)
|
||||
client->notifyClientPoke(_this.lock(), cmd["msg"]);
|
||||
client->notifyClientPoke(_this.lock(), message);
|
||||
|
||||
return command_result{std::forward<command_result_bulk>(result)};
|
||||
}
|
||||
@@ -356,130 +367,58 @@ command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) {
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
//ftgetfilelist cid=1 cpw path=\/ return_code=1:x
|
||||
//Answer:
|
||||
//1 .. n
|
||||
// notifyfilelist cid=1 path=\/ return_code=1:x name=testFile size=35256 datetime=1509459767 type=1|name=testDir size=0 datetime=1509459741 type=0|name=testDir_2 size=0 datetime=1509459763 type=0
|
||||
//notifyfilelistfinished cid=1 path=\/
|
||||
inline void cmd_filelist_append_files(ServerId sid, Command &command, vector<std::shared_ptr<file::FileEntry>> files) {
|
||||
int index = 0;
|
||||
|
||||
logTrace(sid, "Sending file list for path {}", command["path"].string());
|
||||
for (const auto& fileEntry : files) {
|
||||
logTrace(sid, " - {} ({})", fileEntry->name, fileEntry->type == file::FileType::FILE ? "file" : "directory");
|
||||
|
||||
command[index]["name"] = fileEntry->name;
|
||||
command[index]["datetime"] = std::chrono::duration_cast<std::chrono::seconds>(fileEntry->lastChanged.time_since_epoch()).count();
|
||||
command[index]["type"] = fileEntry->type;
|
||||
if (fileEntry->type == file::FileType::FILE)
|
||||
command[index]["size"] = static_pointer_cast<file::File>(fileEntry)->fileSize;
|
||||
else
|
||||
command[index]["size"] = 0;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
#define CMD_REQ_FSERVER if(!serverInstance->getFileServer()) return command_result{error::vs_critical, "file server not started yet!"}
|
||||
|
||||
//start=0 duration=10
|
||||
//pattern=%asd%
|
||||
|
||||
struct ClientDbArgs {
|
||||
shared_ptr<VirtualServer> server;
|
||||
int index = 0;
|
||||
int offset = 0;
|
||||
int resultIndex = 0;
|
||||
bool showIp = false;
|
||||
bool largeInfo = false;
|
||||
Command *result = nullptr;
|
||||
};
|
||||
|
||||
command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
|
||||
CMD_REQ_SERVER;
|
||||
CMD_RESET_IDLE;
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dblist, 1);
|
||||
|
||||
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdblist" : "");
|
||||
size_t offset = cmd[0].has("start") ? cmd["start"].as<size_t>() : 0;
|
||||
size_t limit = cmd[0].has("duration") ? cmd["duration"].as<int>() : 0;
|
||||
if(limit > 2000 || limit < 1)
|
||||
limit = 2000;
|
||||
|
||||
if (!cmd[0].has("start"))
|
||||
cmd["start"] = 0;
|
||||
if (!cmd[0].has("duration"))
|
||||
cmd["duration"] = 20;
|
||||
if (cmd[0]["duration"].as<int>() > 2000) cmd["duration"] = 2000;
|
||||
if (cmd[0]["duration"].as<int>() < 1) cmd["duration"] = 1;
|
||||
ts::command_builder result{this->notify_response_command("notifyclientdblist")};
|
||||
result.reserve_bulks(limit);
|
||||
|
||||
auto maxIndex = cmd["start"].as<uint32_t>() + cmd["duration"].as<uint32_t>();
|
||||
ClientDbArgs args;
|
||||
args.server = this->server;
|
||||
args.offset = cmd["start"].as<uint32_t>();
|
||||
args.result = ¬ify;
|
||||
args.resultIndex = 0;
|
||||
args.index = 0;
|
||||
args.showIp = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
args.largeInfo = cmd.hasParm("details");
|
||||
struct CallbackArgument {
|
||||
ts::command_builder& result;
|
||||
bool show_ip{false};
|
||||
size_t command_index{0};
|
||||
};
|
||||
|
||||
(LOG_SQL_CMD)(sql::command(this->server->getSql(), "SELECT * FROM `clients` WHERE `serverId` = :sid ORDER BY `cldbid` ASC" + (maxIndex > 0 ? " LIMIT " + to_string(maxIndex) : ""), variable{":sid", this->server->getServerId()}).query(
|
||||
[](ClientDbArgs *pArgs, int length, char **values, char **column) {
|
||||
pArgs->index++;
|
||||
if (pArgs->offset < pArgs->index) {
|
||||
ClientDbId id = 0;
|
||||
string uid, name, ip;
|
||||
string created = "0", lastConnected = "0", connections = "0";
|
||||
for (int index = 0; index < length; index++) {
|
||||
string key = column[index];
|
||||
if (key == "cldbid")
|
||||
id = stoll(values[index]);
|
||||
else if (key == "clientUid")
|
||||
uid = values[index];
|
||||
else if (key == "firstConnect")
|
||||
created = values[index];
|
||||
else if (key == "lastConnect")
|
||||
lastConnected = values[index];
|
||||
else if (key == "connections")
|
||||
connections = values[index];
|
||||
else if (key == "lastName")
|
||||
name = values[index];
|
||||
}
|
||||
CallbackArgument callback_argument{result};
|
||||
callback_argument.show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
serverInstance->databaseHelper()->listDatabaseClients(this->getServerId(), { offset }, { limit }, [](void* ptr_data, const DatabaseClient& client) {
|
||||
auto argument = (CallbackArgument*) ptr_data;
|
||||
|
||||
pArgs->result->operator[](pArgs->resultIndex)["cldbid"] = id;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_unique_identifier"] = uid;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_nickname"] = name;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_created"] = created;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_lastconnected"] = lastConnected;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_totalconnections"] = connections;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_description"] = "";
|
||||
auto bulk = argument->result.bulk(argument->command_index++);
|
||||
bulk.reserve(300);
|
||||
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(pArgs->server, id, ClientType::CLIENT_TEAMSPEAK);
|
||||
if (props) {
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as<string>();
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as<string>();
|
||||
bulk.put_unchecked("cldbid", client.client_database_id);
|
||||
bulk.put_unchecked("client_unique_identifier", client.client_unique_id);
|
||||
bulk.put_unchecked("client_nickname", client.client_nickname);
|
||||
bulk.put_unchecked("client_lastip", argument->show_ip ? client.client_ip : "hidden");
|
||||
bulk.put_unchecked("client_lastconnected", client.client_last_connected);
|
||||
bulk.put_unchecked("client_created", client.client_created);
|
||||
bulk.put_unchecked("client_totalconnections", client.client_total_connections);
|
||||
bulk.put_unchecked("client_login_name", client.client_login_name);
|
||||
bulk.put_unchecked("client_description", client.client_description);
|
||||
}, &callback_argument);
|
||||
|
||||
if (pArgs->largeInfo) {
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
||||
}
|
||||
}
|
||||
if (!pArgs->showIp)
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = "hidden";
|
||||
pArgs->resultIndex++;
|
||||
}
|
||||
return 0;
|
||||
}, &args));
|
||||
if (callback_argument.command_index == 0)
|
||||
return command_result{error::database_empty_result};
|
||||
|
||||
if (args.resultIndex == 0) return command_result{error::database_empty_result};
|
||||
if (cmd.hasParm("count")) {
|
||||
size_t result = 0;
|
||||
sql::command(this->server->getSql(), "SELECT COUNT(*) AS `count` FROM `clients` WHERE `serverId` = :sid", variable{":sid", this->server->getServerId()}).query([](size_t *ptr, int, char **v, char **) {
|
||||
*ptr = static_cast<size_t>(stoll(v[0]));
|
||||
return 0;
|
||||
}, &result);
|
||||
notify[0]["count"] = result;
|
||||
size_t count{0};
|
||||
sql::command(this->server->getSql(), "SELECT COUNT(`client_database_id`) AS `count` FROM `clients_server` WHERE `server_id` = :sid", variable{":sid", this->server->getServerId()})
|
||||
.query([&](int, std::string* v, std::string*) {
|
||||
count = stoll(v[0]);
|
||||
});
|
||||
result.put_unchecked(0, "count", count);
|
||||
}
|
||||
this->sendCommand(notify);
|
||||
|
||||
this->sendCommand(result);
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -604,6 +543,16 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if(info == property::CLIENT_NICKNAME_PHONETIC) {
|
||||
if(!self) {
|
||||
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
||||
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
||||
ACTION_REQUIRES_PERMISSION(permission::i_client_music_rename_power, client->calculate_permission(permission::i_client_music_needed_rename_power, client->getChannelId()), client->getChannelId());
|
||||
}
|
||||
}
|
||||
|
||||
string name = cmd["client_nickname_phonetic"].string();
|
||||
if (count_characters(name) > 30) return command_result{error::parameter_invalid, "Invalid name length. A maximum of 30 characters is allowed!"};
|
||||
} else if(info == property::CLIENT_PLAYER_VOLUME) {
|
||||
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
||||
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
||||
@@ -715,10 +664,13 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
} else if(info == property::CLIENT_AWAY_MESSAGE) {
|
||||
if(!self) continue;
|
||||
|
||||
if(cmd["client_away_message"].string().length() > 256)
|
||||
if(cmd["client_away_message"].string().length() > ts::config::server::limits::afk_message_length)
|
||||
return command_result{error::parameter_invalid};
|
||||
} else if(!self) { /* dont edit random properties of other clients. For us self its allowed to edit the rest without permissions */
|
||||
continue;
|
||||
} else if(info == property::CLIENT_TALK_REQUEST_MSG) {
|
||||
if(cmd["client_talk_request_msg"].string().length() > ts::config::server::limits::talk_power_request_message_length)
|
||||
return command_result{error::parameter_invalid};
|
||||
}
|
||||
|
||||
keys.emplace_back(&info, key);
|
||||
@@ -729,11 +681,27 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
if(*key.first == property::CLIENT_IS_PRIORITY_SPEAKER) {
|
||||
client->clientPermissions->set_permission(permission::b_client_is_priority_speaker, {1, 0}, cmd["client_is_priority_speaker"].as<bool>() ? permission::v2::PermissionUpdateType::set_value : permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing);
|
||||
}
|
||||
client->properties()[key.first] = cmd[0][key.second].value();
|
||||
|
||||
auto property = client->properties()[key.first];
|
||||
auto old_value = property.value();
|
||||
auto new_value = cmd[0][key.second].value();
|
||||
if(old_value == new_value)
|
||||
continue;
|
||||
|
||||
property = new_value;
|
||||
updates.push_back(key.first);
|
||||
|
||||
serverInstance->action_logger()->client_edit_logger.log_client_edit(
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
client,
|
||||
*key.first,
|
||||
old_value,
|
||||
new_value
|
||||
);
|
||||
}
|
||||
if(update_talk_rights)
|
||||
client->updateTalkRights(client->properties()[property::CLIENT_TALK_POWER]);
|
||||
client->updateTalkRights(client->calculate_permission(permission::i_client_talk_power, client->getChannelId()));
|
||||
|
||||
if(this->server)
|
||||
this->server->notifyClientPropertyUpdates(client, updates);
|
||||
@@ -864,8 +832,9 @@ command_result ConnectedClient::handleCommandClientGetDBIDfromUID(Command &cmd)
|
||||
CMD_RESET_IDLE;
|
||||
|
||||
deque<string> unique_ids;
|
||||
for(int index = 0; index < cmd.bulkCount(); index++)
|
||||
unique_ids.push_back(cmd[index]["cluid"].as<string>());
|
||||
for(int index = 0; index < cmd.bulkCount(); index++) {
|
||||
unique_ids.push_back(cmd[index]["cluid"]);
|
||||
}
|
||||
|
||||
auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids);
|
||||
if (res.empty()) return command_result{error::database_empty_result};
|
||||
@@ -873,8 +842,8 @@ command_result ConnectedClient::handleCommandClientGetDBIDfromUID(Command &cmd)
|
||||
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdbidfromuid" : "");
|
||||
int result_index = 0;
|
||||
for(auto& info : res) {
|
||||
result[result_index]["cluid"] = info->uniqueId;
|
||||
result[result_index]["cldbid"] = info->cldbid;
|
||||
result[result_index]["cluid"] = info->client_unique_id;
|
||||
result[result_index]["cldbid"] = info->client_database_id;
|
||||
result_index++;
|
||||
}
|
||||
this->sendCommand(result);
|
||||
@@ -895,10 +864,10 @@ command_result ConnectedClient::handleCommandClientGetNameFromDBID(Command &cmd)
|
||||
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetnamefromdbid" : "");
|
||||
int result_index = 0;
|
||||
for(auto& info : res) {
|
||||
result[result_index]["cluid"] = info->uniqueId;
|
||||
result[result_index]["cldbid"] = info->cldbid;
|
||||
result[result_index]["name"] = info->lastName;
|
||||
result[result_index]["clname"] = info->lastName;
|
||||
result[result_index]["cluid"] = info->client_unique_id;
|
||||
result[result_index]["cldbid"] = info->client_database_id;
|
||||
result[result_index]["name"] = info->client_nickname;
|
||||
result[result_index]["clname"] = info->client_nickname;
|
||||
result_index++;
|
||||
}
|
||||
this->sendCommand(result);
|
||||
@@ -919,10 +888,10 @@ command_result ConnectedClient::handleCommandClientGetNameFromUid(Command &cmd)
|
||||
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientnamefromuid" : "");
|
||||
int result_index = 0;
|
||||
for(auto& info : res) {
|
||||
result[result_index]["cluid"] = info->uniqueId;
|
||||
result[result_index]["cldbid"] = info->cldbid;
|
||||
result[result_index]["name"] = info->lastName;
|
||||
result[result_index]["clname"] = info->lastName;
|
||||
result[result_index]["cluid"] = info->client_unique_id;
|
||||
result[result_index]["cldbid"] = info->client_database_id;
|
||||
result[result_index]["name"] = info->client_nickname;
|
||||
result[result_index]["clname"] = info->client_nickname;
|
||||
result_index++;
|
||||
}
|
||||
this->sendCommand(result);
|
||||
@@ -937,7 +906,7 @@ command_result ConnectedClient::handleCommandClientGetUidFromClid(Command &cmd)
|
||||
bool found = false;
|
||||
auto client_list = this->server->getClients();
|
||||
|
||||
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetuidfromclid" : "");
|
||||
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientuidfromclid" : "");
|
||||
int result_index = 0;
|
||||
|
||||
for(int index = 0; index < cmd.bulkCount(); index++) {
|
||||
@@ -982,6 +951,15 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
|
||||
bool update_channels{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::set_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::CLIENT,
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
cldbid, "",
|
||||
0, ""
|
||||
);
|
||||
|
||||
update_channels |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
@@ -1017,6 +995,14 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
|
||||
bool update_channels{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::delete_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::CLIENT,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
cldbid, "",
|
||||
0, ""
|
||||
);
|
||||
update_channels |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
@@ -1064,16 +1050,16 @@ command_result ConnectedClient::handleCommandClientDbInfo(Command &cmd) {
|
||||
|
||||
size_t index = 0;
|
||||
for(const auto& info : basic) {
|
||||
res[index]["client_base64HashClientUID"] = hex::hex(base64::validate(info->uniqueId) ? base64::decode(info->uniqueId) : info->uniqueId, 'a', 'q');
|
||||
res[index]["client_unique_identifier"] = info->uniqueId;
|
||||
res[index]["client_nickname"] = info->lastName;
|
||||
res[index]["client_database_id"] = info->cldbid;
|
||||
res[index]["client_created"] = chrono::duration_cast<chrono::seconds>(info->created.time_since_epoch()).count();
|
||||
res[index]["client_lastconnected"] = chrono::duration_cast<chrono::seconds>(info->lastjoin.time_since_epoch()).count();
|
||||
res[index]["client_totalconnections"] = info->connections;
|
||||
res[index]["client_database_id"] = info->cldbid;
|
||||
res[index]["client_base64HashClientUID"] = hex::hex(base64::validate(info->client_unique_id) ? base64::decode(info->client_unique_id) : info->client_unique_id, 'a', 'q');
|
||||
res[index]["client_unique_identifier"] = info->client_unique_id;
|
||||
res[index]["client_nickname"] = info->client_nickname;
|
||||
res[index]["client_database_id"] = info->client_database_id;
|
||||
res[index]["client_created"] = chrono::duration_cast<chrono::seconds>(info->client_created.time_since_epoch()).count();
|
||||
res[index]["client_lastconnected"] = chrono::duration_cast<chrono::seconds>(info->client_last_connected.time_since_epoch()).count();
|
||||
res[index]["client_totalconnections"] = info->client_total_connections;
|
||||
res[index]["client_database_id"] = info->client_database_id;
|
||||
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, info->cldbid, ClientType::CLIENT_TEAMSPEAK);
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, info->client_database_id, ClientType::CLIENT_TEAMSPEAK);
|
||||
if (allow_ip)
|
||||
res[index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as<string>();
|
||||
else
|
||||
@@ -1114,13 +1100,6 @@ command_result ConnectedClient::handleCommandClientDBDelete(Command &cmd) {
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
struct DBFindArgs {
|
||||
int index = 0;
|
||||
bool full = false;
|
||||
bool ip = false;
|
||||
Command cmd{""};
|
||||
};
|
||||
|
||||
command_result ConnectedClient::handleCommandClientDBFind(Command &cmd) {
|
||||
CMD_REQ_SERVER;
|
||||
CMD_RESET_IDLE;
|
||||
@@ -1130,48 +1109,73 @@ command_result ConnectedClient::handleCommandClientDBFind(Command &cmd) {
|
||||
bool uid = cmd.hasParm("uid");
|
||||
string pattern = cmd["pattern"];
|
||||
|
||||
DBFindArgs args{};
|
||||
args.cmd = Command(this->getType() == CLIENT_QUERY ? "" : "notifyclientdbfind");
|
||||
args.full = cmd.hasParm("details");
|
||||
args.ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
auto res = sql::command(this->sql, "SELECT * FROM `clients` WHERE `serverId` = :sid AND `" + std::string{uid ? "clientUid" : "lastName"} + "` LIKE :pattern LIMIT 50",
|
||||
variable{":sid", this->server->getServerId()},
|
||||
variable{":pattern", pattern}).query(
|
||||
[&](DBFindArgs *ptr, int len, char **values, char **names) {
|
||||
for (int index = 0; index < len; index++)
|
||||
if (strcmp(names[index], "cldbid") == 0)
|
||||
ptr->cmd[ptr->index]["cldbid"] = values[index];
|
||||
else if (strcmp(names[index], "clientUid") == 0 && ptr->full)
|
||||
ptr->cmd[ptr->index]["client_unique_identifier"] = values[index];
|
||||
else if (strcmp(names[index], "lastConnect") == 0 && ptr->full)
|
||||
ptr->cmd[ptr->index]["client_lastconnected"] = values[index];
|
||||
else if (strcmp(names[index], "connections") == 0 && ptr->full)
|
||||
ptr->cmd[ptr->index]["client_totalconnections"] = values[index];
|
||||
else if (strcmp(names[index], "lastName") == 0 && ptr->full)
|
||||
ptr->cmd[ptr->index]["client_nickname"] = values[index];
|
||||
if (ptr->full) {
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, ptr->cmd[ptr->index]["cldbid"], ClientType::CLIENT_TEAMSPEAK);
|
||||
if (props) {
|
||||
if (ptr->ip) {
|
||||
ptr->cmd[ptr->index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as<string>();
|
||||
} else {
|
||||
ptr->cmd[ptr->index]["client_lastip"] = "hidden";
|
||||
}
|
||||
ptr->cmd[ptr->index]["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
||||
ptr->cmd[ptr->index]["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
||||
ptr->cmd[ptr->index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
||||
ptr->cmd[ptr->index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
||||
}
|
||||
const auto detailed = cmd.hasParm("details");
|
||||
const auto show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
|
||||
size_t command_index{0};
|
||||
ts::command_builder result{this->notify_response_command("notifyclientdbfind")};
|
||||
result.reserve_bulks(50);
|
||||
|
||||
constexpr static auto kBaseCommand{"SELECT `client_database_id`, `client_unique_id`, `client_nickname`, `client_ip`, `client_last_connected`, `client_total_connections` FROM `clients_server` WHERE "};
|
||||
|
||||
auto sql_result = sql::command{this->sql, std::string{kBaseCommand} + "`server_id` = :sid AND " + (uid ? "`client_unique_id`" : "`client_nickname`") + " LIKE :pattern LIMIT 50", variable{":sid", this->getServerId()}, variable{":pattern", pattern}}
|
||||
.query([&](int length, std::string* values, std::string* names) {
|
||||
auto bulk = result.bulk(command_index++);
|
||||
bulk.reserve(300);
|
||||
|
||||
auto index{0};
|
||||
ClientDbId client_database_id;
|
||||
try {
|
||||
assert(names[index] == "client_database_id");
|
||||
client_database_id = std::stoull(values[index]);
|
||||
bulk.put_unchecked("cldbid", values[index++]);
|
||||
|
||||
assert(names[index] == "client_unique_id");
|
||||
bulk.put_unchecked("client_unique_identifier", values[index++]);
|
||||
|
||||
assert(names[index] == "client_nickname");
|
||||
bulk.put_unchecked("client_nickname", values[index++]);
|
||||
|
||||
assert(names[index] == "client_ip");
|
||||
if(detailed) {
|
||||
bulk.put_unchecked("client_lastip", show_ip ? values[index++] : "hidden");
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
ptr->index++;
|
||||
return 0;
|
||||
}, &args);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(res);
|
||||
if (args.index == 0) return command_result{error::database_empty_result};
|
||||
|
||||
this->sendCommand(args.cmd);
|
||||
assert(names[index] == "client_last_connected");
|
||||
bulk.put_unchecked("client_lastconnected", values[index++]);
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
bulk.put_unchecked("client_totalconnections", values[index++]);
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
command_index--;
|
||||
logError(this->getServerId(), "Failed to parse client base properties at index {}: {}. Search pattern: {}",
|
||||
index - 1,
|
||||
ex.what(),
|
||||
pattern
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if(detailed) {
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, client_database_id, ClientType::CLIENT_TEAMSPEAK);
|
||||
if (props) {
|
||||
auto& properties = *props;
|
||||
bulk.put_unchecked("client_badges", properties[property::CLIENT_BADGES].as<std::string>());
|
||||
bulk.put_unchecked("client_version", properties[property::CLIENT_VERSION].as<std::string>());
|
||||
bulk.put_unchecked("client_platform", properties[property::CLIENT_PLATFORM].as<std::string>());
|
||||
bulk.put_unchecked("client_hwid", properties[property::CLIENT_HARDWARE_ID].as<std::string>());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(command_index == 0)
|
||||
return command_result{error::database_empty_result};
|
||||
|
||||
this->sendCommand(result);
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -79,7 +79,8 @@ if (permission::resolvePermissionData(permType)->type == permission::PermissionT
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||
inline bool permission_require_granted_value(ts::permission::PermissionType type) {
|
||||
using namespace ts;
|
||||
/*
|
||||
@@ -192,6 +193,7 @@ inline bool permission_is_client_property(ts::permission::PermissionType type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
|
||||
inline ssize_t count_characters(const std::string& in) {
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <spdlog/sinks/rotating_file_sink.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <bitset>
|
||||
#include <algorithm>
|
||||
@@ -13,34 +11,26 @@
|
||||
#include "../../build.h"
|
||||
#include "../ConnectedClient.h"
|
||||
#include "../InternalClient.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "../../server/VoiceServer.h"
|
||||
#include "../voice/VoiceClient.h"
|
||||
#include "PermissionManager.h"
|
||||
#include "../../InstanceHandler.h"
|
||||
#include "../../server/QueryServer.h"
|
||||
#include "../file/FileClient.h"
|
||||
#include "../music/MusicClient.h"
|
||||
#include "../query/QueryClient.h"
|
||||
#include "../../weblist/WebListManager.h"
|
||||
#include "../../manager/ConversationManager.h"
|
||||
#include "../../manager/PermissionNameMapper.h"
|
||||
#include "../../manager/ActionLogger.h"
|
||||
#include <experimental/filesystem>
|
||||
#include <cstdint>
|
||||
#include <StringVariable.h>
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
#include <Properties.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/sassert.h>
|
||||
#include <misc/base64.h>
|
||||
#include <misc/hex.h>
|
||||
#include <misc/digest.h>
|
||||
#include <misc/rnd.h>
|
||||
#include <misc/timer.h>
|
||||
#include <misc/strobf.h>
|
||||
#include <misc/scope_guard.h>
|
||||
#include <bbcode/bbcodes.h>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
@@ -133,9 +123,38 @@ command_result ConnectedClient::handleCommand(Command &cmd) {
|
||||
else if (command == "ftgetfilelist") return this->handleCommandFTGetFileList(cmd);
|
||||
else if (command == "ftcreatedir") return this->handleCommandFTCreateDir(cmd);
|
||||
else if (command == "ftdeletefile") return this->handleCommandFTDeleteFile(cmd);
|
||||
else if (command == "ftinitupload") return this->handleCommandFTInitUpload(cmd);
|
||||
else if (command == "ftinitdownload") return this->handleCommandFTInitDownload(cmd);
|
||||
else if (command == "ftinitupload") {
|
||||
auto result = this->handleCommandFTInitUpload(cmd);
|
||||
if(result.has_error() && this->getType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||
ts::command_builder notify{"notifystatusfiletransfer"};
|
||||
notify.put_unchecked(0, "clientftfid", cmd["clientftfid"].string());
|
||||
notify.put(0, "size", 0);
|
||||
this->writeCommandResult(notify, result, "status");
|
||||
this->sendCommand(notify);
|
||||
result.release_data();
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (command == "ftinitdownload") {
|
||||
auto result = this->handleCommandFTInitDownload(cmd);
|
||||
if(result.has_error() && this->getType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||
ts::command_builder notify{"notifystatusfiletransfer"};
|
||||
notify.put_unchecked(0, "clientftfid", cmd["clientftfid"].string());
|
||||
notify.put(0, "size", 0);
|
||||
this->writeCommandResult(notify, result, "status");
|
||||
this->sendCommand(notify);
|
||||
result.release_data();
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (command == "ftgetfileinfo") return this->handleCommandFTGetFileInfo(cmd);
|
||||
else if (command == "ftrenamefile") return this->handleCommandFTRenameFile(cmd);
|
||||
else if (command == "ftlist") return this->handleCommandFTList(cmd);
|
||||
else if (command == "ftstop") return this->handleCommandFTStop(cmd);
|
||||
//Banlist
|
||||
else if (command == "banlist") return this->handleCommandBanList(cmd);
|
||||
else if (command == "banadd") return this->handleCommandBanAdd(cmd);
|
||||
@@ -210,6 +229,9 @@ command_result ConnectedClient::handleCommand(Command &cmd) {
|
||||
else if (command == "help") return this->handleCommandHelp(cmd);
|
||||
|
||||
else if (command == "logview") return this->handleCommandLogView(cmd);
|
||||
else if (command == "logquery") return this->handleCommandLogQuery(cmd);
|
||||
else if (command == "logadd") return this->handleCommandLogAdd(cmd);
|
||||
|
||||
else if (command == "servergroupautoaddperm") return this->handleCommandServerGroupAutoAddPerm(cmd);
|
||||
else if (command == "servergroupautodelperm") return this->handleCommandServerGroupAutoDelPerm(cmd);
|
||||
|
||||
@@ -248,7 +270,11 @@ command_result ConnectedClient::handleCommand(Command &cmd) {
|
||||
else if (command == "conversationfetch") return this->handleCommandConversationFetch(cmd);
|
||||
else if (command == "conversationmessagedelete") return this->handleCommandConversationMessageDelete(cmd);
|
||||
|
||||
if (this->getType() == ClientType::CLIENT_QUERY) return command_result{error::command_not_found}; //Dont log query invalid commands
|
||||
else if (command == "listfeaturesupport") return this->handleCommandListFeatureSupport(cmd);
|
||||
|
||||
if (this->getType() == ClientType::CLIENT_QUERY)
|
||||
return command_result{error::command_not_found}; //Dont log query invalid commands
|
||||
|
||||
if (this->getType() == ClientType::CLIENT_TEAMSPEAK)
|
||||
if (command.empty() || command.find_first_not_of(' ') == -1) {
|
||||
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_allow_invalid_packet, this->getChannelId())))
|
||||
@@ -371,6 +397,8 @@ do { \
|
||||
response[index]["name"] = prop->name; \
|
||||
response[index]["flags"] = prop->flags; \
|
||||
response[index]["type"] = property::PropertyType_Names[prop->type_property]; \
|
||||
response[index]["value"] = prop->default_value; \
|
||||
response[index]["value_type"] = property::ValueType_Names[(int) prop->type_value]; \
|
||||
index++; \
|
||||
} \
|
||||
} while(0)
|
||||
@@ -380,8 +408,11 @@ command_result ConnectedClient::handleCommandPropertyList(ts::Command& cmd) {
|
||||
|
||||
{
|
||||
string pattern;
|
||||
for (auto flag_name : property::flag_names)
|
||||
pattern = flag_name + string("|") + pattern;
|
||||
for (auto flag_name : property::flag_names) {
|
||||
if(flag_name) {
|
||||
pattern = flag_name + string("|") + pattern;
|
||||
}
|
||||
}
|
||||
pattern = pattern.substr(0, pattern.length() - 1);
|
||||
response["flag_set"] = pattern;
|
||||
}
|
||||
@@ -399,6 +430,8 @@ command_result ConnectedClient::handleCommandPropertyList(ts::Command& cmd) {
|
||||
M(property::GroupProperties);
|
||||
if(cmd.hasParm("all") || cmd.hasParm("connection"))
|
||||
M(property::ConnectionProperties);
|
||||
if(cmd.hasParm("all") || cmd.hasParm("playlist"))
|
||||
M(property::PlaylistProperties);
|
||||
|
||||
this->sendCommand(response);
|
||||
return command_result{error::ok};
|
||||
@@ -445,23 +478,28 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd)
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<GroupAssignment> old_group;
|
||||
{
|
||||
auto old_group = this->server->groups->getChannelGroupExact(target_cldbid, channel, false);
|
||||
old_group = this->server->groups->getChannelGroupExact(target_cldbid, channel, false);
|
||||
if(old_group) {
|
||||
auto channel_group_member_remove_power = this->calculate_permission(permission::i_channel_group_member_remove_power, channel_id);
|
||||
if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_member_remove_power, true)) {
|
||||
if(!old_group->group->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_member_remove_power, true)) {
|
||||
if(target_cldbid != this->getClientDatabaseId())
|
||||
return command_result{permission::i_channel_group_member_remove_power};
|
||||
|
||||
auto channel_group_self_remove_power = this->calculate_permission(permission::i_channel_group_self_remove_power, channel_id);
|
||||
if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_self_remove_power, true))
|
||||
if(!old_group->group->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_self_remove_power, true))
|
||||
return command_result{permission::i_channel_group_self_remove_power};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->server->groups->setChannelGroup(target_cldbid, serverGroup, channel);
|
||||
|
||||
std::shared_ptr<ConnectedClient> connected_client{};
|
||||
for (const auto &targetClient : this->server->findClientsByCldbId(target_cldbid)) {
|
||||
connected_client = targetClient;
|
||||
|
||||
unique_lock client_channel_lock_w(targetClient->channel_lock);
|
||||
auto updates = this->server->groups->update_server_group_property(targetClient, false, targetClient->getChannel()); /* needs a write lock */
|
||||
client_channel_lock_w.unlock();
|
||||
@@ -484,6 +522,21 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd)
|
||||
targetClient->updateChannelClientProperties(false, true);
|
||||
}
|
||||
|
||||
if(old_group) {
|
||||
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove(this->getServerId(),
|
||||
this->ref(), log::GroupTarget::CHANNEL,
|
||||
old_group->group->groupId(), old_group->group->name(),
|
||||
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
|
||||
);
|
||||
}
|
||||
if(serverGroup != this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL)) {
|
||||
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_add(this->getServerId(),
|
||||
this->ref(), log::GroupTarget::CHANNEL,
|
||||
serverGroup->groupId(), serverGroup->name(),
|
||||
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
|
||||
);
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -533,8 +586,10 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) {
|
||||
target->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), 0, timestamp, cmd["msg"].string());
|
||||
this->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), 0, timestamp, cmd["msg"].string());
|
||||
} else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_CHANNEL) {
|
||||
if(!cmd[0].has("cid"))
|
||||
if(!cmd[0].has("cid")) {
|
||||
cmd["cid"] = 0;
|
||||
}
|
||||
|
||||
RESOLVE_CHANNEL_R(cmd["cid"], false);
|
||||
auto channel = l_channel ? dynamic_pointer_cast<BasicChannel>(l_channel->entry) : nullptr;
|
||||
if(!channel) {
|
||||
@@ -548,18 +603,23 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) {
|
||||
|
||||
if(channel == this->currentChannel) {
|
||||
channel_tree_read_lock.unlock(); //Method may creates a music bot which modifies the channel tree
|
||||
if(this->handleTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, cmd["msg"], nullptr))
|
||||
if(this->handleTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, cmd["msg"], nullptr)) {
|
||||
return command_result{error::ok};
|
||||
}
|
||||
channel_tree_read_lock.lock();
|
||||
}
|
||||
|
||||
bool conversation_private = channel->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as<bool>();
|
||||
if(channel != this->currentChannel) {
|
||||
if(conversation_private)
|
||||
auto conversation_mode = channel->properties()[property::CHANNEL_CONVERSATION_MODE].as<ChannelConversationMode>();
|
||||
if(conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE) {
|
||||
return command_result{error::conversation_not_exists};
|
||||
} else if(channel != this->currentChannel) {
|
||||
if(conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE) {
|
||||
return command_result{error::conversation_is_private};
|
||||
}
|
||||
|
||||
if(auto fail_perm{this->calculate_and_get_join_state(channel)}; fail_perm != permission::ok)
|
||||
if(auto fail_perm{this->calculate_and_get_join_state(channel)}; fail_perm != permission::ok) {
|
||||
return command_result{fail_perm}; /* You're not allowed to send messages :) */
|
||||
}
|
||||
}
|
||||
|
||||
this->server->send_text_message(channel, this->ref(), cmd["msg"].string());
|
||||
@@ -668,8 +728,9 @@ command_result ConnectedClient::handleCommandBanAdd(Command &cmd) {
|
||||
return command_result{permission::b_client_ban_create};
|
||||
}
|
||||
|
||||
auto max_ban_time = server->calculate_permission(permission::i_client_ban_max_bantime, this->getClientDatabaseId(), this->getType(), 0);
|
||||
if(!max_ban_time.has_value) return command_result{permission::i_client_ban_max_bantime};
|
||||
auto max_ban_time = this->calculate_permission(permission::i_client_ban_max_bantime, this->getClientDatabaseId());
|
||||
if(!max_ban_time.has_value)
|
||||
return command_result{permission::i_client_ban_max_bantime};
|
||||
if (!max_ban_time.has_infinite_power()) {
|
||||
if (max_ban_time.value < time)
|
||||
return command_result{permission::i_client_ban_max_bantime};
|
||||
@@ -681,8 +742,9 @@ command_result ConnectedClient::handleCommandBanAdd(Command &cmd) {
|
||||
bool banned = false;
|
||||
if(existing) {
|
||||
if(existing->invokerDbId == this->getClientDatabaseId()) {
|
||||
if(existing->until == until) return command_result{error::database_duplicate_entry};
|
||||
else {
|
||||
if(existing->until == until) {
|
||||
return command_result{error::database_duplicate_entry};
|
||||
} else {
|
||||
existing->until = until;
|
||||
serverInstance->banManager()->updateBan(existing);
|
||||
banned = true;
|
||||
@@ -691,7 +753,9 @@ command_result ConnectedClient::handleCommandBanAdd(Command &cmd) {
|
||||
serverInstance->banManager()->unban(existing);
|
||||
}
|
||||
}
|
||||
if(!banned) serverInstance->banManager()->registerBan(sid, this->getClientDatabaseId(), banreason, uid, ip, name, hwid, until);
|
||||
if(!banned) {
|
||||
serverInstance->banManager()->registerBan(sid, this->getClientDatabaseId(), banreason, uid, ip, name, hwid, until);
|
||||
}
|
||||
|
||||
for(auto server : (this->server ? std::deque<shared_ptr<VirtualServer>>{this->server} : serverInstance->getVoiceServerManager()->serverInstances()))
|
||||
server->testBanStateChange(_this.lock());
|
||||
@@ -1835,10 +1899,10 @@ command_result ConnectedClient::handleCommandComplainList(Command &cmd) {
|
||||
result[index]["timestamp"] = chrono::duration_cast<chrono::seconds>(elm->created.time_since_epoch()).count();
|
||||
|
||||
for (const auto &e : dbInfo) {
|
||||
if (e->cldbid == elm->target)
|
||||
result[index]["tname"] = e->lastName;
|
||||
if (e->cldbid == elm->invoker)
|
||||
result[index]["fname"] = e->lastName;
|
||||
if (e->client_database_id == elm->target)
|
||||
result[index]["tname"] = e->client_nickname;
|
||||
if (e->client_database_id == elm->invoker)
|
||||
result[index]["fname"] = e->client_nickname;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -1931,19 +1995,21 @@ command_result ConnectedClient::handleCommandPermReset(ts::Command& cmd) {
|
||||
command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(50);
|
||||
|
||||
auto lagacy = this->getType() == CLIENT_TEAMSPEAK || cmd.hasParm("lagacy") || cmd.hasParm("legacy");
|
||||
string log_path;
|
||||
ServerId target_server = cmd[0].has("instance") && cmd["instance"].as<bool>() ? (ServerId) 0 : this->getServerId();
|
||||
string server_identifier;
|
||||
if(target_server > 0)
|
||||
server_identifier = to_string(target_server);
|
||||
else server_identifier = "[A-Z]{0,7}";
|
||||
|
||||
if(target_server == 0)
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_view, 1);
|
||||
else
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_view, 1);
|
||||
|
||||
auto lagacy = this->getType() == CLIENT_TEAMSPEAK || cmd.hasParm("lagacy") || cmd.hasParm("legacy");
|
||||
#if 0
|
||||
string log_path;
|
||||
string server_identifier;
|
||||
if(target_server > 0)
|
||||
server_identifier = to_string(target_server);
|
||||
else
|
||||
server_identifier = "[A-Z]{0,7}";
|
||||
|
||||
for(const auto& sink : logger::logger(target_server)->sinks()) {
|
||||
if(dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_mt>(sink)) {
|
||||
log_path = dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_mt>(sink)->filename();
|
||||
@@ -2074,7 +2140,130 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
}
|
||||
}
|
||||
this->sendCommand(result);
|
||||
#else
|
||||
constexpr static std::array<std::string_view, 5> log_output{
|
||||
"located at your TeaSpeak installation folder. All logs could be found there.",
|
||||
"If you need to lookup the TeaSpeak - Server logs, please visit the 'logs/' folder,",
|
||||
"",
|
||||
"In order to lookup the server actions use 'logquery'.",
|
||||
"The command 'logview' is not supported anymore."
|
||||
};
|
||||
|
||||
command_builder result{this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyserverlog" : ""};
|
||||
result.put_unchecked(0, "last_pos", 0);
|
||||
result.put_unchecked(0, "file_size", 0);
|
||||
|
||||
size_t index{0};
|
||||
if(lagacy) {
|
||||
for(const auto& message : log_output) {
|
||||
std::string line{"2020-06-27 00:00.000" + std::to_string(index) + "|CRITICAL|Server Instance | |"};
|
||||
line += message;
|
||||
result.put_unchecked(index++, "l", line);
|
||||
}
|
||||
} else {
|
||||
for(const auto& message : log_output) {
|
||||
std::string line{"[2020-06-27 00:00:0" + std::to_string(index) + "][ERROR] "};
|
||||
line += message;
|
||||
result.put_unchecked(index++, "l", line);
|
||||
}
|
||||
}
|
||||
this->sendCommand(result);
|
||||
#endif
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandLogQuery(ts::Command &cmd) {
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(50);
|
||||
|
||||
uint64_t target_server = (cmd[0].has("instance") && cmd["instance"].as<bool>()) || cmd.hasParm("instance") ? (ServerId) 0 : this->getServerId();
|
||||
if(target_server == 0) {
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_view, 1);
|
||||
} else {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_view, 1);
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point timestamp_begin{}, timestamp_end{};
|
||||
|
||||
if(cmd[0].has("begin"))
|
||||
timestamp_begin += std::chrono::milliseconds{cmd["begin"].as<uint64_t>()};
|
||||
|
||||
if(cmd[0].has("end"))
|
||||
timestamp_end += std::chrono::milliseconds{cmd["end"].as<uint64_t>()};
|
||||
|
||||
if(timestamp_begin <= timestamp_end && timestamp_begin.time_since_epoch().count() != 0)
|
||||
return command_result{error::parameter_constraint_violation, "begin > end"};
|
||||
|
||||
size_t limit{100};
|
||||
if(cmd[0].has("limit"))
|
||||
limit = std::min((size_t) 2000, cmd["limit"].as<size_t>());
|
||||
|
||||
std::vector<log::LoggerGroup> groups{};
|
||||
if(cmd[0].has("groups")) {
|
||||
auto groups_string = cmd["groups"].string();
|
||||
size_t offset{0}, findex;
|
||||
do {
|
||||
findex = groups_string.find(',', offset);
|
||||
|
||||
auto group_name = groups_string.substr(offset, findex - offset);
|
||||
offset = findex + 1;
|
||||
|
||||
|
||||
size_t index{0};
|
||||
for(; index < (size_t) log::LoggerGroup::MAX; index++) {
|
||||
auto group = static_cast<log::LoggerGroup>(index);
|
||||
if(log::kLoggerGroupName[(int) group] == group_name) {
|
||||
if(std::find(groups.begin(), groups.end(), group) != groups.end())
|
||||
return command_result{error::parameter_invalid, "groups"};
|
||||
|
||||
groups.push_back(group);
|
||||
}
|
||||
}
|
||||
|
||||
if(index == (size_t) log::LoggerGroup::MAX)
|
||||
return command_result{error::parameter_invalid, "groups"};
|
||||
} while(offset != 0);
|
||||
}
|
||||
|
||||
auto result = serverInstance->action_logger()->query(groups, target_server, timestamp_begin, timestamp_end, limit);
|
||||
if(result.empty())
|
||||
return command_result{error::database_empty_result};
|
||||
|
||||
command_builder notify{this->notify_response_command("notifylogquery")};
|
||||
size_t index{0};
|
||||
size_t threshold = this->getType() == ClientType::CLIENT_QUERY ? (size_t) -1 : 4096; /* limit each command to 4096 bytes */
|
||||
for(log::LogEntryInfo& entry : result) {
|
||||
notify.set_bulk(index, std::move(entry.info));
|
||||
if(index == 0)
|
||||
notify.put_unchecked(0, "sid", this->getServerId());
|
||||
|
||||
if(notify.current_size() > threshold) {
|
||||
this->sendCommand(notify);
|
||||
notify.reset();
|
||||
index = 0;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if(index> 0)
|
||||
this->sendCommand(notify);
|
||||
|
||||
if(this->getType() != ClientType::CLIENT_QUERY)
|
||||
this->sendCommand(command_builder{"notifylogqueryfinished"});
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandLogAdd(ts::Command& cmd) {
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(50);
|
||||
|
||||
uint64_t target_server = cmd[0].has("instance") && cmd["instance"].as<bool>() ? (ServerId) 0 : this->getServerId();
|
||||
if(target_server == 0) {
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_add, 1);
|
||||
} else {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_add, 1);
|
||||
}
|
||||
|
||||
serverInstance->action_logger()->custom_logger.add_log_message(target_server, this->ref(), cmd["msg"]);
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -2177,7 +2366,7 @@ command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) {
|
||||
auto info = serverInstance->databaseHelper()->queryDatabaseInfo(server, {cmd["cldbid"].as<ClientDbId>()});
|
||||
if(info.empty())
|
||||
return command_result{error::database_empty_result};
|
||||
uid = info[0]->uniqueId;
|
||||
uid = info[0]->client_unique_id;
|
||||
} else {
|
||||
if(server) {
|
||||
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
|
||||
@@ -2364,15 +2553,25 @@ command_result ConnectedClient::handleCommandConversationHistory(ts::Command &co
|
||||
|
||||
auto conversation_id = command[0]["cid"].as<ChannelId>();
|
||||
/* test if we have access to the conversation */
|
||||
{
|
||||
if(conversation_id > 0) {
|
||||
/* test if we're able to see the channel */
|
||||
{
|
||||
shared_lock channel_view_lock(this->channel_lock);
|
||||
auto channel = this->channel_view()->find_channel(conversation_id);
|
||||
if(!channel)
|
||||
return command_result{error::conversation_invalid_id};
|
||||
if(channel->channel()->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as<bool>())
|
||||
return command_result{error::conversation_is_private};
|
||||
|
||||
auto conversation_mode = channel->channel()->properties()[property::CHANNEL_CONVERSATION_MODE].as<ChannelConversationMode>();
|
||||
switch (conversation_mode) {
|
||||
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE:
|
||||
return command_result{error::conversation_is_private};
|
||||
|
||||
case ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE:
|
||||
return command_result{error::conversation_not_exists};
|
||||
|
||||
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PUBLIC:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* test if there is a channel password or join power which denies that we see the conversation */
|
||||
@@ -2480,7 +2679,7 @@ command_result ConnectedClient::handleCommandConversationFetch(ts::Command &cmd)
|
||||
result_bulk["cid"] = conversation_id;
|
||||
|
||||
/* test if we have access to the conversation */
|
||||
{
|
||||
if(conversation_id > 0) {
|
||||
/* test if we're able to see the channel */
|
||||
{
|
||||
shared_lock channel_view_lock(this->channel_lock);
|
||||
@@ -2491,11 +2690,25 @@ command_result ConnectedClient::handleCommandConversationFetch(ts::Command &cmd)
|
||||
result_bulk["error_msg"] = error.message;
|
||||
continue;
|
||||
}
|
||||
if(channel->channel()->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as<bool>()) {
|
||||
auto error = findError("conversation_is_private");
|
||||
result_bulk["error_id"] = error.errorId;
|
||||
result_bulk["error_msg"] = error.message;
|
||||
continue;
|
||||
|
||||
auto conversation_mode = channel->channel()->properties()[property::CHANNEL_CONVERSATION_MODE].as<ChannelConversationMode>();
|
||||
switch (conversation_mode) {
|
||||
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE: {
|
||||
auto error = findError("conversation_is_private");
|
||||
result_bulk["error_id"] = error.errorId;
|
||||
result_bulk["error_msg"] = error.message;
|
||||
continue;
|
||||
}
|
||||
|
||||
case ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE: {
|
||||
auto error = findError("conversation_not_exists");
|
||||
result_bulk["error_id"] = error.errorId;
|
||||
result_bulk["error_msg"] = error.message;
|
||||
continue;
|
||||
}
|
||||
|
||||
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PUBLIC:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2532,11 +2745,13 @@ command_result ConnectedClient::handleCommandConversationFetch(ts::Command &cmd)
|
||||
}
|
||||
|
||||
auto conversation = conversation_manager->get(conversation_id);
|
||||
if(conversation)
|
||||
if(conversation) {
|
||||
result_bulk["timestamp"] = duration_cast<milliseconds>(conversation->last_message().time_since_epoch()).count();
|
||||
else
|
||||
result_bulk["flag_volatile"] = conversation->volatile_only();
|
||||
} else {
|
||||
result_bulk["timestamp"] = 0;
|
||||
result_bulk["flag_volatile"] = conversation->volatile_only();
|
||||
result_bulk["flag_volatile"] = false;
|
||||
}
|
||||
}
|
||||
if(result_index == 0)
|
||||
return command_result{error::database_empty_result};
|
||||
@@ -2585,7 +2800,7 @@ command_result ConnectedClient::handleCommandConversationMessageDelete(ts::Comma
|
||||
if (!channel->passwordMatch(bulk["cpw"], true))
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_channel_join_ignore_password, 1, channel->channelId());
|
||||
|
||||
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_conversation_message_delete, 1, channel->channelId())))
|
||||
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_conversation_message_delete, channel->channelId())))
|
||||
return command_result{permission::b_channel_conversation_message_delete};
|
||||
|
||||
if(auto error_perm = this->calculate_and_get_join_state(channel); error_perm != permission::ok && error_perm != permission::b_client_is_sticky)
|
||||
@@ -2601,6 +2816,7 @@ command_result ConnectedClient::handleCommandConversationMessageDelete(ts::Comma
|
||||
auto limit = bulk.has("limit") ? bulk["limit"].as<uint64_t>() : 1;
|
||||
if(limit > 100)
|
||||
limit = 100;
|
||||
|
||||
auto delete_count = current_conversation->delete_messages(timestamp_end, limit, timestamp_begin, bulk["cldbid"]);
|
||||
if(delete_count > 0) {
|
||||
for(const auto& client : ref_server->getClients()) {
|
||||
@@ -2619,6 +2835,33 @@ command_result ConnectedClient::handleCommandConversationMessageDelete(ts::Comma
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
enum struct FeatureSupportMode {
|
||||
NONE,
|
||||
FULL,
|
||||
EXPERIMENTAL,
|
||||
DEPRECATED
|
||||
};
|
||||
|
||||
#define REGISTER_FEATURE(name, support, version) \
|
||||
notify.put_unchecked(index, "name", name); \
|
||||
notify.put_unchecked(index, "support", (int) support); \
|
||||
notify.put_unchecked(index, "version", version); \
|
||||
index++
|
||||
|
||||
command_result ConnectedClient::handleCommandListFeatureSupport(ts::Command &cmd) {
|
||||
|
||||
ts::command_builder notify{this->notify_response_command("notifyfeaturesupport")};
|
||||
int index{0};
|
||||
|
||||
REGISTER_FEATURE("error-bulks", FeatureSupportMode::FULL, 1);
|
||||
REGISTER_FEATURE("advanced-channel-chat", FeatureSupportMode::FULL, 1);
|
||||
REGISTER_FEATURE("log-query", FeatureSupportMode::FULL, 1);
|
||||
REGISTER_FEATURE("whisper-echo", FeatureSupportMode::FULL, 1);
|
||||
|
||||
this->sendCommand(notify);
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2635,5 +2878,3 @@ command_result ConnectedClient::handleCommandConversationMessageDelete(ts::Comma
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,13 +13,11 @@
|
||||
#include "../../build.h"
|
||||
#include "../ConnectedClient.h"
|
||||
#include "../InternalClient.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "../../server/VoiceServer.h"
|
||||
#include "../voice/VoiceClient.h"
|
||||
#include "PermissionManager.h"
|
||||
#include "../../InstanceHandler.h"
|
||||
#include "../../server/QueryServer.h"
|
||||
#include "../file/FileClient.h"
|
||||
#include "../music/MusicClient.h"
|
||||
#include "../query/QueryClient.h"
|
||||
#include "../../weblist/WebListManager.h"
|
||||
@@ -77,7 +75,7 @@ command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) {
|
||||
permission::i_client_music_create_modify_max_volume
|
||||
}, this->getChannelId());
|
||||
|
||||
auto permissions = map<permission::PermissionType, permission::v2::PermissionFlaggedValue>(permissions_list.begin(), permissions_list.end());
|
||||
auto permissions = std::map<permission::PermissionType, permission::v2::PermissionFlaggedValue>(permissions_list.begin(), permissions_list.end());
|
||||
|
||||
auto max_bots = permissions[permission::i_client_music_limit];
|
||||
if(max_bots.has_value) {
|
||||
@@ -563,8 +561,17 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) {
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions())
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::PLAYLIST,
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
playlist->playlist_id(), "",
|
||||
0, ""
|
||||
);
|
||||
}
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
@@ -584,8 +591,17 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) {
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions())
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::PLAYLIST,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
playlist->playlist_id(), "",
|
||||
0, ""
|
||||
);
|
||||
}
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
@@ -707,8 +723,17 @@ command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command &
|
||||
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
|
||||
return pparser.build_command_result();
|
||||
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions())
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value, client_id);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::PLAYLIST_CLIENT,
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
playlist->playlist_id(), "",
|
||||
client_id, ""
|
||||
);
|
||||
}
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
@@ -732,8 +757,17 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &
|
||||
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
|
||||
return pparser.build_command_result();
|
||||
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions())
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value, client_id);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::PLAYLIST_CLIENT,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
playlist->playlist_id(), "",
|
||||
client_id, ""
|
||||
);
|
||||
}
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
@@ -10,17 +10,16 @@
|
||||
#include "../../build.h"
|
||||
#include "../ConnectedClient.h"
|
||||
#include "../InternalClient.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "../../server/VoiceServer.h"
|
||||
#include "../voice/VoiceClient.h"
|
||||
#include "../../InstanceHandler.h"
|
||||
#include "../../server/QueryServer.h"
|
||||
#include "../file/FileClient.h"
|
||||
#include "../music/MusicClient.h"
|
||||
#include "../query/QueryClient.h"
|
||||
#include "../../weblist/WebListManager.h"
|
||||
#include "../../manager/ConversationManager.h"
|
||||
#include "../../manager/PermissionNameMapper.h"
|
||||
#include "../../manager/ActionLogger.h"
|
||||
|
||||
#include "helpers.h"
|
||||
#include "./bulk_parsers.h"
|
||||
@@ -69,6 +68,7 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
|
||||
target_server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
|
||||
if(!target_server && cmd["sid"].as<ServerId>() != 0) return command_result{error::server_invalid_id};
|
||||
}
|
||||
ServerId serverId = target_server ? target_server->serverId : 0;
|
||||
|
||||
auto cache = make_shared<CalculateCache>();
|
||||
map<string, string> toApplay;
|
||||
@@ -222,10 +222,13 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
|
||||
logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + std::string{info.name} + "')");
|
||||
continue;
|
||||
}
|
||||
if(target_server)
|
||||
target_server->properties()[info] = elm.second;
|
||||
else
|
||||
(*serverInstance->getDefaultServerProperties())[info] = elm.second;
|
||||
|
||||
auto property = target_server ? target_server->properties()[info] : (*serverInstance->getDefaultServerProperties())[info];
|
||||
if(property.value() == elm.second)
|
||||
continue;
|
||||
auto old_value = property.value();
|
||||
property = elm.second;
|
||||
serverInstance->action_logger()->server_edit_logger.log_server_edit(serverId, this->ref(), info, old_value, elm.second);
|
||||
keys.push_back(elm.first);
|
||||
|
||||
group_update |= info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP;
|
||||
@@ -261,8 +264,8 @@ command_result ConnectedClient::handleCommandServerRequestConnectionInfo(Command
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT, minute_report.file_bytes_sent);
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, minute_report.file_bytes_received);
|
||||
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, minute_report.file_bytes_sent);
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, minute_report.file_bytes_received);
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, this->server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED].as<string>());
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, this->server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED].as<string>());
|
||||
|
||||
first_bulk.put_unchecked("connection_filetransfer_bytes_sent_month", this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as<string>());
|
||||
first_bulk.put_unchecked("connection_filetransfer_bytes_received_month", this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as<string>());
|
||||
@@ -301,25 +304,64 @@ command_result ConnectedClient::handleCommandServerGroupAdd(Command &cmd) {
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_create, 1);
|
||||
|
||||
if(cmd["name"].string().empty()) return command_result{error::parameter_invalid};
|
||||
if(cmd["name"].string().empty()) {
|
||||
return command_result{error::parameter_invalid, "name"};
|
||||
}
|
||||
|
||||
log::GroupType log_group_type;
|
||||
if(!cmd[0].has("type")) {
|
||||
cmd["type"] = GroupType::GROUP_TYPE_NORMAL;
|
||||
}
|
||||
|
||||
if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_QUERY) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
} else if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_TEMPLATE) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
} else if(!this->server) return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"};
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
} else {
|
||||
if(!this->server) {
|
||||
return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"};
|
||||
}
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
}
|
||||
|
||||
auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get();
|
||||
for(const auto& gr : group_manager->availableServerGroups(true))
|
||||
if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_SERVER) return command_result{error::parameter_invalid, "Group already exists"};
|
||||
for(const auto& gr : group_manager->availableServerGroups(true)) {
|
||||
if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_SERVER) {
|
||||
return command_result{error::parameter_invalid, "Group already exists"};
|
||||
}
|
||||
}
|
||||
|
||||
auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_SERVER, cmd["type"].as<GroupType>(), cmd["name"].string());
|
||||
if (group) {
|
||||
group->permissions()->set_permission(permission::b_group_is_permanent, {1,0}, permission::v2::set_value, permission::v2::do_nothing);
|
||||
if(this->server)
|
||||
this->server->forEachClient([](shared_ptr<ConnectedClient> cl) {
|
||||
if(!group) {
|
||||
return command_result{error::vs_critical};
|
||||
}
|
||||
|
||||
group->permissions()->set_permission(permission::b_group_is_permanent, {1,0}, permission::v2::set_value, permission::v2::do_nothing);
|
||||
|
||||
{
|
||||
ts::command_builder notify{this->notify_response_command("notifyservergroupadded")};
|
||||
notify.put_unchecked(0, "sgid", group->groupId());
|
||||
this->sendCommand(notify);
|
||||
}
|
||||
|
||||
if(this->server) {
|
||||
if(this->getType() == ClientType::CLIENT_QUERY) {
|
||||
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
|
||||
if(cl == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
cl->notifyServerGroupList();
|
||||
});
|
||||
} else return command_result{error::vs_critical};
|
||||
} else {
|
||||
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
|
||||
cl->notifyServerGroupList();
|
||||
});
|
||||
}
|
||||
}
|
||||
serverInstance->action_logger()->group_logger.log_group_create(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, group->groupId(), group->name(), 0, "");
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -349,6 +391,7 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) {
|
||||
return permission::b_serverinstance_modify_querygroup;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_NORMAL:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -380,6 +423,28 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) {
|
||||
if(!group_manager->copyGroupPermissions(source_group, target_group))
|
||||
return command_result{error::vs_critical, "failed to copy group permissions"};
|
||||
|
||||
|
||||
log::GroupType log_group_type;
|
||||
switch (target_group->type()) {
|
||||
case GroupType::GROUP_TYPE_QUERY:
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_TEMPLATE:
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_NORMAL:
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
break;
|
||||
|
||||
default:
|
||||
return command_result{error::parameter_invalid, "type"};
|
||||
}
|
||||
|
||||
serverInstance->action_logger()->group_logger.log_group_permission_copy(target_group->type() != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(),
|
||||
this->ref(), log::GroupTarget::SERVER, log_group_type, target_group->groupId(), target_group->name(), source_group->groupId(), source_group->name());
|
||||
|
||||
global_update = !this->server || !group_manager->isLocalGroup(target_group);
|
||||
} else {
|
||||
//Copy a new group
|
||||
@@ -390,6 +455,23 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) {
|
||||
if(result != permission::undefined)
|
||||
return command_result{result};
|
||||
}
|
||||
log::GroupType log_group_type;
|
||||
switch (target_type) {
|
||||
case GroupType::GROUP_TYPE_QUERY:
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_TEMPLATE:
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
break;
|
||||
|
||||
case GroupType::GROUP_TYPE_NORMAL:
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
break;
|
||||
|
||||
default:
|
||||
return command_result{error::parameter_invalid, "type"};
|
||||
}
|
||||
|
||||
if(!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL)
|
||||
return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"};
|
||||
@@ -401,6 +483,8 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) {
|
||||
if(target_group_id == 0)
|
||||
return command_result{error::vs_critical, "failed to copy group"};
|
||||
|
||||
serverInstance->action_logger()->group_logger.log_group_create(target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(),
|
||||
this->ref(), log::GroupTarget::SERVER, log_group_type, target_group_id, cmd["name"].string(), source_group->groupId(), source_group->name());
|
||||
if(this->getType() == ClientType::CLIENT_QUERY) {
|
||||
Command notify("");
|
||||
notify["sgid"] = target_group_id;
|
||||
@@ -429,13 +513,20 @@ command_result ConnectedClient::handleCommandServerGroupRename(Command &cmd) {
|
||||
ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true);
|
||||
|
||||
auto type = serverGroup->type();
|
||||
log::GroupType log_group_type;
|
||||
if(type == GroupType::GROUP_TYPE_QUERY) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
} else {
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
}
|
||||
|
||||
auto old_name = serverGroup->name();
|
||||
group_manager->renameGroup(serverGroup, cmd["name"].string());
|
||||
serverInstance->action_logger()->group_logger.log_group_rename(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, serverGroup->groupId(), serverGroup->name(), old_name);
|
||||
if(this->server)
|
||||
this->server->forEachClient([](shared_ptr<ConnectedClient> cl) {
|
||||
cl->notifyServerGroupList();
|
||||
@@ -453,21 +544,30 @@ command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) {
|
||||
auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get();
|
||||
auto serverGroup = group_manager->findGroup(cmd["sgid"].as<GroupId>());
|
||||
if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::group_invalid_id};
|
||||
ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true);
|
||||
|
||||
if(this->server && this->server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] == serverGroup->groupId())
|
||||
return command_result{error::parameter_invalid, "Could not delete default server group!"};
|
||||
|
||||
if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP] == serverGroup->groupId())
|
||||
return command_result{error::parameter_invalid, "Could not delete instance default server admin group!"};
|
||||
|
||||
if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP] == serverGroup->groupId())
|
||||
return command_result{error::parameter_invalid, "Could not delete instance default server group!"};
|
||||
|
||||
if(serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] == serverGroup->groupId())
|
||||
return command_result{error::parameter_invalid, "Could not delete instance default guest server query group!"};
|
||||
|
||||
auto type = serverGroup->type();
|
||||
log::GroupType log_group_type;
|
||||
if(type == GroupType::GROUP_TYPE_QUERY) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
|
||||
log_group_type = log::GroupType::QUERY;
|
||||
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
log_group_type = log::GroupType::TEMPLATE;
|
||||
} else {
|
||||
log_group_type = log::GroupType::NORMAL;
|
||||
}
|
||||
|
||||
if (!cmd["force"].as<bool>())
|
||||
@@ -475,6 +575,7 @@ command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) {
|
||||
return command_result{error::database_empty_result, "group not empty!"};
|
||||
|
||||
if (group_manager->deleteGroup(serverGroup)) {
|
||||
serverInstance->action_logger()->group_logger.log_group_delete(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, serverGroup->groupId(), serverGroup->name());
|
||||
if(this->server)
|
||||
this->server->forEachClient([&](shared_ptr<ConnectedClient> cl) {
|
||||
if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) {
|
||||
@@ -506,11 +607,16 @@ command_result ConnectedClient::handleCommandServerGroupClientList(Command &cmd)
|
||||
notify["sgid"] = cmd["sgid"].as<GroupId>();
|
||||
int index = 0;
|
||||
for (const auto &clientEntry : groupManager->listGroupMembers(serverGroup)) {
|
||||
notify[index]["cldbid"] = clientEntry->cldbId;
|
||||
notify[index]["client_nickname"] = clientEntry->displayName;
|
||||
notify[index]["client_unique_identifier"] = clientEntry->uid;
|
||||
notify[index]["cldbid"] = clientEntry.cldbId;
|
||||
notify[index]["client_nickname"] = clientEntry.displayName;
|
||||
notify[index]["client_unique_identifier"] = clientEntry.uid;
|
||||
index++;
|
||||
}
|
||||
|
||||
if(index == 0 && this->getType() != ClientType::CLIENT_TEAMSPEAK) {
|
||||
return ts::command_result{error::database_empty_result};
|
||||
}
|
||||
|
||||
this->sendCommand(notify);
|
||||
return command_result{error::ok};
|
||||
}
|
||||
@@ -613,8 +719,10 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd)
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectedClient> connected_client{};
|
||||
for(const auto& _server : target_server ? std::deque<shared_ptr<VirtualServer>>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) {
|
||||
for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) {
|
||||
connected_client = targetClient;
|
||||
if (_server->notifyClientPropertyUpdates(targetClient, _server->groups->update_server_group_property(targetClient, true, targetClient->getChannel()))) {
|
||||
for (const auto &client : _server->getClients()) {
|
||||
if(client->isClientVisible(targetClient, true) || client == targetClient)
|
||||
@@ -627,6 +735,13 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
for(const auto& group : applied_groups) {
|
||||
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_add(target_server ? target_server->getServerId() : 0,
|
||||
this->ref(), log::GroupTarget::SERVER,
|
||||
group->groupId(), group->name(),
|
||||
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
|
||||
);
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
@@ -740,8 +855,10 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd)
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectedClient> connected_client{};
|
||||
for(const auto& _server : target_server ? std::deque<shared_ptr<VirtualServer>>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) {
|
||||
for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) {
|
||||
connected_client = targetClient;
|
||||
ConnectedLockedClient clock{targetClient};
|
||||
if(!clock) continue;
|
||||
|
||||
@@ -757,6 +874,13 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
for(const auto& group : applied_groups) {
|
||||
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove(target_server ? target_server->getServerId() : 0,
|
||||
this->ref(), log::GroupTarget::SERVER,
|
||||
group->groupId(), group->name(),
|
||||
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
|
||||
);
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
@@ -786,12 +910,14 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) {
|
||||
if (serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid};
|
||||
ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, 1);
|
||||
|
||||
/* We don't need this. The modify permissions only apply when creating/editing/renaming the groups itself
|
||||
auto type = serverGroup->type();
|
||||
if(type == GroupType::GROUP_TYPE_QUERY) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
|
||||
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
}
|
||||
*/
|
||||
|
||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
@@ -801,6 +927,15 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) {
|
||||
auto permissions = serverGroup->permissions();
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::SERVER_GROUP,
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
serverGroup->groupId(), serverGroup->name(),
|
||||
0, ""
|
||||
);
|
||||
|
||||
update_server_group_list |= ppermission.is_group_property();
|
||||
update_talk_power |= ppermission.is_client_view_property();
|
||||
}
|
||||
@@ -841,12 +976,14 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
|
||||
if (serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid};
|
||||
ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, 1);
|
||||
|
||||
/* We don't need this. The modify permissions only apply when creating/editing/renaming the groups itself
|
||||
auto type = serverGroup->type();
|
||||
if(type == GroupType::GROUP_TYPE_QUERY) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
|
||||
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
}
|
||||
*/
|
||||
|
||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
@@ -855,6 +992,15 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
|
||||
bool update_talk_power{false}, update_server_group_list{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::SERVER_GROUP,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
serverGroup->groupId(), serverGroup->name(),
|
||||
0, ""
|
||||
);
|
||||
|
||||
update_server_group_list |= ppermission.is_group_property();
|
||||
update_talk_power |= ppermission.is_client_view_property();
|
||||
}
|
||||
@@ -919,8 +1065,17 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command&
|
||||
|
||||
bool update_clients{false}, update_server_group_list{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
for(const auto& serverGroup : groups)
|
||||
for(const auto& serverGroup : groups) {
|
||||
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::SERVER_GROUP,
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
serverGroup->groupId(), serverGroup->name(),
|
||||
0, ""
|
||||
);
|
||||
}
|
||||
|
||||
update_server_group_list |= ppermission.is_group_property();
|
||||
update_clients |= ppermission.is_client_view_property();
|
||||
@@ -989,8 +1144,17 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command&
|
||||
|
||||
bool update_clients{false}, update_server_group_list{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
for(const auto& serverGroup : groups)
|
||||
for(const auto& serverGroup : groups) {
|
||||
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
|
||||
ppermission.log_update(serverInstance->action_logger()->permission_logger,
|
||||
this->getServerId(),
|
||||
this->ref(),
|
||||
log::PermissionTarget::SERVER_GROUP,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
serverGroup->groupId(), serverGroup->name(),
|
||||
0, ""
|
||||
);
|
||||
}
|
||||
|
||||
update_server_group_list |= ppermission.is_group_property();
|
||||
update_clients |= ppermission.is_client_view_property();
|
||||
|
||||
@@ -1,719 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <src/server/file/LocalFileServer.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "FileClient.h"
|
||||
#include <src/InstanceHandler.h>
|
||||
#include <experimental/filesystem>
|
||||
#include <misc/memtracker.h>
|
||||
#include <misc/base64.h>
|
||||
#include "src/client/ConnectedClient.h"
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts::server;
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
#define BUFFER_SIZE (size_t) 2048
|
||||
FileClient::FileClient(LocalFileServer* handle, int socketFd) : handle(handle), clientFd(socketFd) {
|
||||
memtrack::allocated<FileClient>(this);
|
||||
this->last_io_action = system_clock::now();
|
||||
|
||||
int enabled = 1;
|
||||
if(setsockopt(socketFd, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof enabled) < 0)
|
||||
logError(LOG_FT, "{} Cant enable TCP no delay for socket {}. Error: {}/{}", this->client_prefix(), socketFd, errno, strerror(errno));
|
||||
|
||||
this->readEvent = event_new(this->handle->ioLoop, socketFd, EV_READ|EV_PERSIST, [](int a, short b, void* c){ ((FileClient*) c)->handleMessageRead(a, b, c); }, this);
|
||||
this->writeEvent = event_new(this->handle->ioLoop, socketFd, EV_TIMEOUT | EV_WRITE, [](int a, short b, void* c){ ((FileClient*) c)->handleMessageWrite(a, b, c); }, this);
|
||||
|
||||
|
||||
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
|
||||
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
|
||||
this->ssl_handler.callback_write(bind(&FileClient::sendRawMessage, this, placeholders::_1));
|
||||
this->ssl_handler.callback_data(bind(&FileClient::handle_ssl_message, this, placeholders::_1));
|
||||
/*
|
||||
this->ssl_handler.callback_data([&](const pipes::buffer_view& msg) {
|
||||
if(this->ftType == FTType::TeaWeb_HTTPS) {
|
||||
this->handle_http_message(msg);
|
||||
} else if(this->ftType == FTType::TeaWeb_SSL_WS) {
|
||||
this->ws_handler.process_incoming_data(msg);
|
||||
} else {
|
||||
logError(LOG_FT, "{} Decoded SSL packet, but transfer type isn't SSL!", this->client_prefix());
|
||||
}
|
||||
});
|
||||
*/
|
||||
//FIXME init ssl error handler?
|
||||
|
||||
this->ws_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
|
||||
this->ws_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
|
||||
|
||||
/*
|
||||
this->ws_handler.callback_data([&](const pipes::WSMessage& message) {
|
||||
if(this->state_transfere == T_INITIALIZE) {
|
||||
this->applyKey(message.data.string()); //Got the key :)
|
||||
} else if(this->state_transfere == T_TRANSFER) {
|
||||
if(this->pendingKey->upload)
|
||||
this->uploadWriteBytes(message.data.string());
|
||||
else {
|
||||
logError(LOG_FT, "{} Invalid write (Just download)", this->client_prefix());
|
||||
this->disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
*/
|
||||
this->ws_handler.callback_data(bind(&FileClient::handle_ws_message,this, placeholders::_1));
|
||||
this->ws_handler.callback_write([&](const pipes::buffer_view& data) {
|
||||
this->ssl_handler.send(data);
|
||||
});
|
||||
this->ws_handler.on_connect = [&]() {};
|
||||
this->ws_handler.on_disconnect = [&](const std::string&) {};
|
||||
}
|
||||
|
||||
FileClient::~FileClient() {
|
||||
if(this->thread_flush.joinable()){
|
||||
this->state_connection = C_DISCONNECTED;
|
||||
|
||||
if(this->thread_flush.get_id() != this_thread::get_id())
|
||||
this->thread_flush.join();
|
||||
else
|
||||
this->thread_flush.detach();
|
||||
}
|
||||
memtrack::freed<FileClient>(this);
|
||||
}
|
||||
|
||||
size_t FileClient::used_bandwidth() {
|
||||
auto now = system_clock::now();
|
||||
|
||||
size_t tranfarred_bytes = 0;
|
||||
auto timeout = now - seconds(1);
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(this->bandwidth_lock);
|
||||
for(auto it = this->bandwidth.end(); it != this->bandwidth.begin();) {
|
||||
--it;
|
||||
|
||||
if((*it)->timestamp < timeout) {
|
||||
this->bandwidth.erase(this->bandwidth.begin(), it);
|
||||
break;
|
||||
}
|
||||
tranfarred_bytes += (*it)->length;
|
||||
}
|
||||
}
|
||||
return tranfarred_bytes;
|
||||
}
|
||||
|
||||
std::string FileClient::client_prefix() {
|
||||
bool hide_ip = config::server::disable_ip_saving;
|
||||
if(!hide_ip) {
|
||||
auto client = this->client;
|
||||
if(client) {
|
||||
auto server = client->getServer();
|
||||
if(server) {
|
||||
hide_ip = server->disable_ip_saving();
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string address = "";
|
||||
if(hide_ip)
|
||||
address = "X.X.X.X:" + to_string(net::port(this->remote_address));
|
||||
else
|
||||
address = net::to_string(this->remote_address);
|
||||
if(this->client) return "[" + to_string(this->client->getServerId()) + "|" + address + "| " + this->client->getDisplayName() + "]";
|
||||
return "[0|" + address + "|unconnected]";
|
||||
}
|
||||
|
||||
size_t FileClient::transferred_bytes() {
|
||||
return this->bytesHandled;
|
||||
}
|
||||
|
||||
size_t FileClient::remaining_bytes() {
|
||||
threads::MutexLock lock(this->tickLock);
|
||||
if(!this->pendingKey) return 0;
|
||||
return this->pendingKey->size - this->bytesHandled;
|
||||
}
|
||||
|
||||
void FileClient::disconnect(std::chrono::milliseconds timeout) {
|
||||
auto own_lock = _this.lock();
|
||||
if(!own_lock) return;
|
||||
|
||||
bool apply_flush = timeout.count() > 0;
|
||||
debugMessage(LOG_FT, "{} Disconnecting client. Connection state: {} Flush IO: {}", this->client_prefix(), (int) this->state_connection, apply_flush);
|
||||
unique_lock<threads::Mutex> tick_lock(this->tickLock);
|
||||
|
||||
if(this->state_connection == C_DISCONNECTED) return; /* we're already disconnected */
|
||||
|
||||
if(apply_flush && this->state_connection == C_DISCONNECTING) return; /* we're already flushing the IO */
|
||||
|
||||
if(this->ftType == FTType::TeaWeb_SSL_WS) {
|
||||
this->ws_handler.disconnect(1000, "disconnected");
|
||||
};
|
||||
|
||||
this->state_connection = apply_flush ? C_DISCONNECTING : C_DISCONNECTED;
|
||||
if(this->readEvent)
|
||||
event_del_noblock(this->readEvent);
|
||||
|
||||
if(apply_flush){
|
||||
lock_guard flush_lock(this->thread_flush_lock);
|
||||
assert(!this->thread_flush.joinable());
|
||||
|
||||
this->thread_flush = std::thread([own_lock, timeout]{
|
||||
auto beg = system_clock::now();
|
||||
while(own_lock->state_connection == C_DISCONNECTING && beg + timeout > system_clock::now()){
|
||||
{
|
||||
lock_guard buffer_lock(own_lock->bufferLock);
|
||||
if(own_lock->read_queue.empty() && own_lock->write_queue.empty()) break;
|
||||
}
|
||||
usleep(10 * 1000);
|
||||
}
|
||||
unique_lock<threads::Mutex> l(own_lock->tickLock);
|
||||
if(own_lock->state_connection != C_DISCONNECTING) return;
|
||||
own_lock->disconnectFinal(l, true);
|
||||
});
|
||||
{
|
||||
auto native_handle = this->thread_flush.native_handle();
|
||||
pthread_setname_np(native_handle, "FileClient flush");
|
||||
}
|
||||
} else {
|
||||
unique_lock flush_lock(this->thread_flush_lock);
|
||||
if(this->thread_flush.joinable()) {
|
||||
flush_lock.unlock();
|
||||
this->thread_flush.join();
|
||||
flush_lock.lock();
|
||||
}
|
||||
disconnectFinal(tick_lock, false);
|
||||
}
|
||||
}
|
||||
|
||||
void FileClient::disconnectFinal(unique_lock<threads::Mutex>& l, bool lock_flush_thread) {
|
||||
auto ownLock = _this.lock();
|
||||
unique_lock l1(this->bufferLock);
|
||||
|
||||
this->state_connection = C_DISCONNECTED;
|
||||
{
|
||||
unique_lock flush_lock(this->thread_flush_lock, try_to_lock);
|
||||
if(flush_lock.owns_lock() || !lock_flush_thread) {
|
||||
if(this->thread_flush.joinable()) {
|
||||
if(this->thread_flush.get_id() == this_thread::get_id())
|
||||
this->thread_flush.detach();
|
||||
else
|
||||
this->thread_flush.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this->readEvent){
|
||||
auto event = this->readEvent;
|
||||
this->readEvent = nullptr;
|
||||
l1.unlock();
|
||||
l.unlock();
|
||||
event_del_block(event);
|
||||
event_free(event);
|
||||
l.lock();
|
||||
l1.lock();
|
||||
}
|
||||
|
||||
if(this->writeEvent){
|
||||
auto event = this->writeEvent;
|
||||
this->writeEvent = nullptr;
|
||||
l1.unlock();
|
||||
l.unlock();
|
||||
event_del_block(event);
|
||||
event_free(event);
|
||||
l.lock();
|
||||
l1.lock();
|
||||
}
|
||||
|
||||
if(this->clientFd > 0) {
|
||||
shutdown(this->clientFd, SHUT_RDWR);
|
||||
close(this->clientFd);
|
||||
}
|
||||
this->clientFd = -1;
|
||||
|
||||
{
|
||||
threads::MutexLock l2(this->handle->clientLock);
|
||||
auto& clList = this->handle->connectedClients;
|
||||
auto elm = find(clList.begin(), clList.end(), _this.lock());
|
||||
if(elm != clList.end()) clList.erase(elm);
|
||||
else logError(LOG_FT, "{} Invalid ft client list!", this->client_prefix());
|
||||
}
|
||||
|
||||
this->read_queue.clear();
|
||||
this->write_queue.clear();
|
||||
|
||||
if(this->fstream){
|
||||
this->fstream->flush();
|
||||
this->fstream->close();
|
||||
delete this->fstream;
|
||||
this->fstream = nullptr;
|
||||
}
|
||||
|
||||
this->pendingKey = nullptr;
|
||||
}
|
||||
|
||||
bool FileClient::tick() {
|
||||
lock_guard<threads::Mutex> l(this->tickLock);
|
||||
if(this->state_connection == C_DISCONNECTED) return false;
|
||||
|
||||
if(this->state_connection != C_DISCONNECTING) {
|
||||
if(last_io_action.time_since_epoch().count() == 0)
|
||||
last_io_action = system_clock::now();
|
||||
else if(last_io_action + minutes(1) < system_clock::now()) {
|
||||
logMessage(LOG_FT, "{} Timed out after one minute of silence!", this->client_prefix());
|
||||
this->disconnect();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* decode incomming stuff */
|
||||
bool flag_reexecute = false;
|
||||
{
|
||||
/* types which require an extra layer of decode */
|
||||
if(this->ftType == FTType::TeaWeb_SSL || this->ftType == FTType::TeaWeb_SSL_HTTP || this->ftType == FTType::TeaWeb_SSL_WS || this->ftType == FTType::TeaWeb_HTTP) {
|
||||
pipes::buffer buffer;
|
||||
{
|
||||
lock_guard buffer_lock(this->bufferLock);
|
||||
if(this->read_queue.empty()) {
|
||||
flag_reexecute = false;
|
||||
} else {
|
||||
flag_reexecute = true;
|
||||
|
||||
buffer = this->read_queue.front();
|
||||
this->read_queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
if(flag_reexecute) {
|
||||
if(this->ftType == FTType::TeaWeb_HTTP)
|
||||
this->handle_http_message(buffer);
|
||||
else
|
||||
this->ssl_handler.process_incoming_data(buffer);
|
||||
}
|
||||
} else if(this->ftType == FTType::TeamSpeak) {
|
||||
flag_reexecute |= this->handle_ts_message();
|
||||
} else if(this->ftType == FTType::Unknown) {
|
||||
/* we need at least 16 bytes to detect any type */
|
||||
if(this->availableBytes() >= 16) {
|
||||
auto header = this->peekBytes(16);
|
||||
if(header.find("GET") != -1 || header.find("POST") != -1 || header.find("OPTIONS") != -1) {
|
||||
debugMessage(LOG_FT, "{} Using HTTP only!", this->client_prefix());
|
||||
this->ftType = FTType::TeaWeb_HTTP;
|
||||
return true;
|
||||
} else if(pipes::SSL::isSSLHeader(header) && serverInstance->sslManager()->web_ssl_options()) {
|
||||
debugMessage(LOG_FT, "{} Encrypting pipe with SSL", this->client_prefix());
|
||||
this->ftType = FTType::TeaWeb_SSL;
|
||||
this->ssl_handler.initialize(serverInstance->sslManager()->web_ssl_options());
|
||||
return true;
|
||||
} else {
|
||||
debugMessage(LOG_FT, "{} Transferring data with the TS protocol", this->client_prefix());
|
||||
this->ftType = FTType::TeamSpeak;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logError(LOG_FT, "{} Ticked client with unknown protocol type. Closing connection.", this->client_prefix());
|
||||
this->disconnect();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(this->state_transfer == T_TRANSFER) {
|
||||
if(this->pendingKey) {
|
||||
/* test for download */
|
||||
if(!this->pendingKey->upload) {
|
||||
/* lets send some data :) */
|
||||
|
||||
if(!fstream) {
|
||||
logError(LOG_FT, "{} Missing file stream! Disconnecting client...", this->client_prefix());
|
||||
this->disconnect();
|
||||
return false;
|
||||
}
|
||||
size_t count = 0;
|
||||
{
|
||||
lock_guard lock(this->bufferLock);
|
||||
count = this->write_queue.size();
|
||||
}
|
||||
|
||||
if(count <= (1024 * 512) / BUFFER_SIZE){ //Max buffer 500Kb
|
||||
if(!fstream->good()){
|
||||
logError(LOG_FT, "{} Cant finish file download. File isn't good anymore!", this->client_prefix());
|
||||
this->disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
pipes::buffer writeBuffer(BUFFER_SIZE);
|
||||
auto read = fstream->readsome((char*) writeBuffer.data_ptr(), writeBuffer.length());
|
||||
if(read < 0){
|
||||
logError(LOG_FT, "{} Invalid file read. Read {} bytes of max {}. Index {}/{}", this->client_prefix(), read, writeBuffer.length(), this->bytesHandled, this->pendingKey->size);
|
||||
this->disconnect();
|
||||
return false;
|
||||
} else if(read == 0){
|
||||
if(this->bytesHandled != this->pendingKey->size){
|
||||
logError(LOG_FT, "{} Invalid end of file. Expected {} bytes and attempted to read at {}", this->client_prefix(), this->pendingKey->size, this->bytesHandled);
|
||||
this->disconnect();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->bytesHandled += read;
|
||||
this->client->getConnectionStatistics()->logFileTransferOut(read);
|
||||
if(read > 0){
|
||||
writeBuffer.resize(read);
|
||||
this->sendMessage(writeBuffer);
|
||||
flag_reexecute |= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* lets test if we're done */
|
||||
if(this->bytesHandled == this->pendingKey->size){
|
||||
auto time = duration_cast<milliseconds>(system_clock::now() - this->connect_timestamp).count();
|
||||
logMessage(LOG_FT, "{} File transfer completed. Transferred {} bytes in {} milliseconds. Waiting for disconnect.", this->client_prefix(), this->bytesHandled, time);
|
||||
|
||||
this->close_file_handle();
|
||||
this->pendingKey.reset();
|
||||
this->state_transfer = T_DONE;
|
||||
this->finished_timestamp = system_clock::now();
|
||||
|
||||
/* we expect TS3 to hangup the connection */
|
||||
if(this->ftType != FTType::TeamSpeak)
|
||||
this->disconnect(seconds(5));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if(this->state_transfer == T_DONE) {
|
||||
if(this->finished_timestamp + seconds(2) < system_clock::now() && this->state_connection == C_CONNECTED) {
|
||||
debugMessage(LOG_FT, "{} Disconnecting client after 2 seconds after finish!", this->client_prefix());
|
||||
this->disconnect(seconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
return flag_reexecute;
|
||||
}
|
||||
|
||||
bool FileClient::applyKey(const string &key) {
|
||||
{
|
||||
threads::MutexLock lock(this->handle->keylock);
|
||||
for(const auto& pkey : this->handle->pendingKeys) //needs a copy
|
||||
if(pkey && pkey->key == key){
|
||||
this->pendingKey = pkey;
|
||||
this->handle->pendingKeys.erase(std::find(this->handle->pendingKeys.begin(), this->handle->pendingKeys.end(), pkey));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!this->pendingKey){
|
||||
logError(LOG_FT, "{} Tried to apply an non existing key! (Key: {})", this->client_prefix(), key);
|
||||
this->disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
this->client = this->pendingKey->owner.lock();
|
||||
if(!this->client) {
|
||||
logError(LOG_FT, "{} Tried connect with an invalid key (client offline)! (Key: {})", this->client_prefix(), key);
|
||||
this->disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
debugMessage(LOG_FT, "{} Initialized file transfer for file '{}' (Type: {})", this->client_prefix(), this->pendingKey->targetFile, pendingKey->upload ? "upload" : "download");
|
||||
if(pendingKey->offset == 0 && pendingKey->upload) {
|
||||
try {
|
||||
fs::remove(fs::u8path(pendingKey->targetFile));
|
||||
} catch (std::exception& e) {}
|
||||
}
|
||||
|
||||
fstream = new std::fstream();
|
||||
fstream->open(pendingKey->targetFile, (pendingKey->upload ? ios::out : ios::in) | ios::binary | ios::app);
|
||||
if(!*fstream) {
|
||||
logError(LOG_FT, "{} Failed to open target file {} for {}", this->client_prefix(), this->pendingKey->targetFile, pendingKey->upload ? "upload" : "download");
|
||||
delete fstream;
|
||||
this->fstream = nullptr;
|
||||
return false;
|
||||
}
|
||||
fstream->seekg(0, std::ios::beg);
|
||||
auto fsize = fstream->tellg();
|
||||
fstream->seekg(0, std::ios::end);
|
||||
fsize = fstream->tellg() - fsize;
|
||||
fstream->seekg(this->pendingKey->offset, std::ios::beg);
|
||||
|
||||
if(!*fstream) {
|
||||
logError(LOG_FT, "{} Failed to seek within file {} for {}", this->client_prefix(), this->pendingKey->targetFile, pendingKey->upload ? "upload" : "download");
|
||||
delete fstream;
|
||||
this->fstream = nullptr;
|
||||
return false;
|
||||
}
|
||||
debugMessage(LOG_FT, "{} Received local file size {}. Target size if {}", this->client_prefix(), fsize, pendingKey->size);
|
||||
if(this->state_transfer == T_INITIALIZE)
|
||||
this->state_transfer = T_TRANSFER;
|
||||
|
||||
this->connect_timestamp = system_clock::now();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool FileClient::uploadWriteBytes(const pipes::buffer_view& message) {
|
||||
this->client->getConnectionStatistics()->logFileTransferIn(message.length());
|
||||
if(!fstream->good()){
|
||||
logError(LOG_FT, "{} uploadWriteBytes(...) called with invalid fstream!", this->client_prefix());
|
||||
return false;
|
||||
}
|
||||
|
||||
fstream->write(message.data_ptr<char>(), message.length());
|
||||
if(!fstream->good()){
|
||||
logError(LOG_FT, "{} Invalid file write! ({})", this->client_prefix(), message.length());
|
||||
this->disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
this->bytesHandled += message.length();
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileClient::close_file_handle() {
|
||||
if(this->fstream) {
|
||||
this->fstream->flush();
|
||||
this->fstream->close();
|
||||
delete this->fstream;
|
||||
this->fstream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void FileClient::sendMessage(const pipes::buffer_view& message) {
|
||||
switch(this->ftType) {
|
||||
case FTType::TeamSpeak:
|
||||
case FTType::TeaWeb_HTTP:
|
||||
this->sendRawMessage(message);
|
||||
break;
|
||||
case FTType::TeaWeb_SSL:
|
||||
case FTType::TeaWeb_SSL_HTTP:
|
||||
this->ssl_handler.send(message);
|
||||
break;
|
||||
case FTType::TeaWeb_SSL_WS:
|
||||
this->ws_handler.send({pipes::BINARY, message.own_buffer()});
|
||||
break;
|
||||
default:
|
||||
/* Dont log an error because the timeout disconnect function uses this without knowing which proto */
|
||||
__asm__("nop");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FileClient::handle_ssl_message(const pipes::buffer_view &buffer) {
|
||||
if(this->ftType == FTType::TeaWeb_SSL_HTTP)
|
||||
this->handle_http_message(buffer);
|
||||
else if(this->ftType == FTType::TeaWeb_SSL_WS)
|
||||
this->ws_handler.process_incoming_data(buffer);
|
||||
else if(this->ftType == FTType::TeaWeb_SSL) { /* lets detect if we have HTTP or WebSocket */
|
||||
this->read_buffer += buffer;
|
||||
this->handle_http_header();
|
||||
}
|
||||
}
|
||||
|
||||
void FileClient::handle_http_header() {
|
||||
auto header_end = this->read_buffer.find("\r\n\r\n");
|
||||
if(header_end == string::npos) {
|
||||
if(this->read_buffer.length() > 1024 * 1024 * 4) {
|
||||
this->read_buffer = pipes::buffer{};
|
||||
logMessage(LOG_FT, "{} Client tried to fillup server memory. Disconnecting client.", this->client_prefix());
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto raw_request = this->read_buffer.view(0, header_end).string();
|
||||
http::HttpRequest request{};
|
||||
if(!http::parse_request(raw_request, request)) {
|
||||
logError(LOG_FT, "{} Failed to parse HTTP request. Disconnecting client.", this->client_prefix());
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
auto header_upgrade = request.findHeader("Upgrade");
|
||||
if(header_upgrade && header_upgrade.values[0] == "websocket") {
|
||||
debugMessage(LOG_FT, "{} Received WebSocket upgrade. Upgrading connection.", this->client_prefix(), raw_request);
|
||||
if(this->ftType == FTType::TeaWeb_SSL)
|
||||
this->ftType = FTType::TeaWeb_SSL_WS;
|
||||
else {
|
||||
//TODO: WebSocket only!
|
||||
}
|
||||
this->ws_handler.initialize();
|
||||
this->ws_handler.process_incoming_data(this->read_buffer);
|
||||
this->read_buffer = pipes::buffer{};
|
||||
this->handle->tickFileClient(_this.lock()); /* we require a manual reticking */
|
||||
return;
|
||||
} else {
|
||||
/* process the http request header */
|
||||
if(this->ftType == FTType::TeaWeb_SSL)
|
||||
this->ftType = FTType::TeaWeb_SSL_HTTP;
|
||||
|
||||
http::HttpResponse response{};
|
||||
response.setHeader("Connection", {"close"}); /* close the connection instance, we dont want multiple requests */
|
||||
response.setHeader("Access-Control-Allow-Methods", {"GET, POST"});
|
||||
response.setHeader("Access-Control-Allow-Origin", {"*"});
|
||||
response.setHeader("Access-Control-Allow-Headers", request.findHeader("Access-Control-Request-Headers").values); //access-control-allow-headers
|
||||
response.setHeader("Access-Control-Max-Age", {"86400"});
|
||||
response.setHeader("Access-Control-Expose-Headers", {"*, Content-Length, X-media-bytes, Content-Disposition"});
|
||||
|
||||
/* test for a preflight request: https://developer.mozilla.org/en-US/docs/Glossary/preflight_request */
|
||||
auto request_method = request.findHeader("Access-Control-Request-Method");
|
||||
if(request_method) {
|
||||
debugMessage(LOG_FT, "{} Received preflight request. Sending close response with allow and let the client reconnect.", this->client_prefix());
|
||||
|
||||
response.code = http::code::_200;
|
||||
|
||||
this->read_buffer = this->read_buffer.range(header_end + 4);
|
||||
auto raw_response = response.build();
|
||||
this->sendMessage(pipes::buffer_view{raw_response.data(), raw_response.length()});
|
||||
this->disconnect(seconds(5)); /* write our response & flush */
|
||||
return;
|
||||
} else {
|
||||
auto transfer_key = request.findHeader("transfer-key");
|
||||
auto download_name = request.findHeader("download-name");
|
||||
|
||||
if(!transfer_key) {
|
||||
response.code = http::code::code(400, "Bad Request");
|
||||
debugMessage(LOG_FT, "{} Received invalid HTTP request (Missing transfer key).", this->client_prefix());
|
||||
} else {
|
||||
if(!this->applyKey(transfer_key.values[0]) || !this->pendingKey) {
|
||||
response.code = http::code::code(404, "Not Found");
|
||||
debugMessage(LOG_FT, "{} Received invalid HTTP request (Invalid transfer key: {}).", this->client_prefix(), transfer_key.values[0]);
|
||||
} else {
|
||||
response.code = http::code::code(200, "OK");
|
||||
response.setHeader("Content-Length", {to_string(this->pendingKey->size)});
|
||||
|
||||
if(!this->pendingKey->upload) {
|
||||
response.setHeader("Content-type", {"application/octet-stream; "});
|
||||
response.setHeader("Content-Transfer-Encoding", {"binary"});
|
||||
response.setHeader("Content-Disposition", {"attachment; filename=\"" + http::encode_url(download_name ? download_name.values[0] : "TeaWeb Download") + "\""});
|
||||
|
||||
if(this->pendingKey->size > 1) {
|
||||
char header_buffer[64];
|
||||
auto read = fstream->readsome(header_buffer, 64);
|
||||
if(read > 0)
|
||||
response.setHeader("X-media-bytes", {base64::encode(header_buffer, read)});
|
||||
fstream->seekg(this->pendingKey->offset, std::ios::beg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!this->pendingKey || !this->pendingKey->upload) {
|
||||
auto raw_response = response.build();
|
||||
this->sendMessage(pipes::buffer_view{raw_response.data(), raw_response.length()});
|
||||
}
|
||||
if(response.code->code != 200) {
|
||||
this->disconnect(seconds(5)) /* write our response & flush */;
|
||||
return;
|
||||
}
|
||||
|
||||
this->http_init = true;
|
||||
auto overhead = this->read_buffer.range(header_end + 4);
|
||||
this->read_buffer = pipes::buffer{}; /* reset the read buffer */
|
||||
if(overhead.length() > 0)
|
||||
this->handle_http_message(overhead);
|
||||
this->handle->tickFileClient(_this.lock()); /* we require a manual reticking */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FileClient::handle_ts_message() {
|
||||
if(this->state_transfer == T_INITIALIZE) {
|
||||
if(this->availableBytes() >= 16) {
|
||||
return this->applyKey(this->getBytes(16));
|
||||
} else
|
||||
return false;
|
||||
} else if(this->state_transfer == T_TRANSFER) {
|
||||
if(!this->pendingKey)
|
||||
return false;
|
||||
|
||||
if(!this->pendingKey->upload)
|
||||
return false; /* should never happen! */
|
||||
|
||||
bool reexecute = false;
|
||||
pipes::buffer buffer;
|
||||
{
|
||||
lock_guard buffer_lock(this->bufferLock);
|
||||
if(this->read_queue.empty())
|
||||
return false; /* nothing to upload */
|
||||
|
||||
buffer = this->read_queue.front();
|
||||
this->read_queue.pop_front();
|
||||
reexecute |= !this->read_queue.empty();
|
||||
}
|
||||
|
||||
if(!this->uploadWriteBytes(buffer))
|
||||
return false; /* error already handeled by uploadWriteBytes(...) */
|
||||
|
||||
return reexecute;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileClient::handle_ws_message(const pipes::WSMessage &message) {
|
||||
if(this->state_transfer == T_INITIALIZE) {
|
||||
debugMessage(LOG_FT, "{} Received transfer key: {}", this->client_prefix(), message.data.string());
|
||||
if(this->applyKey(message.data.string()))
|
||||
this->handle->tickFileClient(_this.lock()); /* we require a manual reticking */
|
||||
return;
|
||||
} else if(this->state_transfer == T_TRANSFER) {
|
||||
if(this->pendingKey->upload)
|
||||
this->uploadWriteBytes(message.data);
|
||||
else {
|
||||
logError(LOG_FT, "{} Invalid write (Just download)", this->client_prefix());
|
||||
this->disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileClient::handle_http_message(const pipes::buffer_view &message) {
|
||||
if(!this->http_init) {
|
||||
this->read_buffer += message;
|
||||
this->handle_http_header();
|
||||
return;
|
||||
}
|
||||
if(!https_upload_init) {
|
||||
//------WebKitFormBoundaryaWP8XAzMBnMOJznv\r\nContent-Disposition: form-data; name=\"file\"; filename=\"blob\"\r\nContent-Type: application/octet-stream\r\n\r\n
|
||||
this->read_buffer += message;
|
||||
|
||||
auto header_end = this->read_buffer.find("\r\n\r\n");
|
||||
if(header_end == string::npos) {
|
||||
if(this->read_buffer.length() > 1024 * 1024 * 4) {
|
||||
this->read_buffer = pipes::buffer{};
|
||||
logMessage(LOG_FT, "{} Client tried to fillup server memory. Disconnecting client.", this->client_prefix());
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
https_upload_init = true;
|
||||
auto overhead = this->read_buffer.view(header_end + 4);
|
||||
this->read_buffer = pipes::buffer{};
|
||||
if(!overhead.empty())
|
||||
this->handle_http_message(overhead);
|
||||
return;
|
||||
}
|
||||
if(!this->pendingKey || !this->pendingKey->upload) {
|
||||
logError(LOG_FT, "{} HTTP Invalid request", this->client_prefix());
|
||||
return;
|
||||
}
|
||||
|
||||
auto bytes_to_write = this->pendingKey->size - this->bytesHandled; /* ignore boundaries */
|
||||
if(bytes_to_write < message.length())
|
||||
this->uploadWriteBytes(message.view(0, bytes_to_write));
|
||||
else
|
||||
this->uploadWriteBytes(message);
|
||||
|
||||
if(this->bytesHandled == this->pendingKey->size){
|
||||
http::HttpResponse response{};
|
||||
response.setHeader("Connection", {"close"}); /* close the connection instance, we dont want multiple requests */
|
||||
response.setHeader("Access-Control-Allow-Methods", {"GET, POST"});
|
||||
response.setHeader("Access-Control-Allow-Origin", {"*"});
|
||||
response.setHeader("Access-Control-Allow-Headers", {"*"});
|
||||
response.setHeader("Access-Control-Max-Age", {"86400"});
|
||||
response.setHeader("Access-Control-Expose-Headers", {"*"});
|
||||
|
||||
auto raw_response = response.build();
|
||||
this->sendMessage(pipes::buffer_view{raw_response.data(), raw_response.length()});
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <protocol/buffers.h>
|
||||
#include <poll.h>
|
||||
#include <fstream>
|
||||
#include <src/server/file/LocalFileServer.h>
|
||||
#include <event.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include "src/VirtualServer.h"
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class ConnectedClient;
|
||||
|
||||
class ConnectedClient;
|
||||
class LocalFileServer;
|
||||
|
||||
enum FTType {
|
||||
Unknown,
|
||||
TeamSpeak,
|
||||
|
||||
TeaWeb_SSL, /* not yet decided if the protocol is HTTP or WebSocket */
|
||||
TeaWeb_SSL_WS,
|
||||
TeaWeb_SSL_HTTP,
|
||||
|
||||
TeaWeb_HTTP
|
||||
};
|
||||
|
||||
class FileClient {
|
||||
friend class LocalFileServer;
|
||||
public:
|
||||
enum TransferState {
|
||||
T_INITIALIZE,
|
||||
T_TRANSFER,
|
||||
T_DONE
|
||||
};
|
||||
enum ConnectionState {
|
||||
C_CONNECTED,
|
||||
C_DISCONNECTING,
|
||||
C_DISCONNECTED
|
||||
};
|
||||
struct BandwidthEntry {
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
uint16_t length = 0;
|
||||
};
|
||||
|
||||
FileClient(LocalFileServer* handle, int socketFd);
|
||||
~FileClient();
|
||||
|
||||
void disconnect(std::chrono::milliseconds = std::chrono::milliseconds(5000));
|
||||
FTType getFTType(){ return this->ftType; }
|
||||
|
||||
size_t used_bandwidth();
|
||||
|
||||
std::string client_prefix();
|
||||
|
||||
size_t transferred_bytes();
|
||||
size_t remaining_bytes();
|
||||
protected:
|
||||
void disconnectFinal(std::unique_lock<threads::Mutex>& /* tick lock */, bool /* handle flush thread */);
|
||||
|
||||
bool tick();
|
||||
|
||||
bool uploadWriteBytes(const pipes::buffer_view&);
|
||||
void close_file_handle();
|
||||
|
||||
bool applyKey(const std::string &);
|
||||
void sendMessage(const pipes::buffer_view&);
|
||||
|
||||
//Direct methods & IO stuff
|
||||
void sendRawMessage(const pipes::buffer_view&);
|
||||
void handleMessageRead(int, short, void*);
|
||||
void handleMessageWrite(int, short, void*);
|
||||
|
||||
void handle_ssl_message(const pipes::buffer_view&); /* handeles all decoded SSL messages */
|
||||
|
||||
/* http header parser. header must be stored with read buffer! */
|
||||
void handle_http_header();
|
||||
|
||||
/* Final protocol handlers */
|
||||
void handle_http_message(const pipes::buffer_view&);
|
||||
void handle_ws_message(const pipes::WSMessage&);
|
||||
bool handle_ts_message();
|
||||
private:
|
||||
LocalFileServer* handle;
|
||||
std::weak_ptr<FileClient> _this;
|
||||
|
||||
std::recursive_mutex bandwidth_lock;
|
||||
std::deque<std::unique_ptr<BandwidthEntry>> bandwidth;
|
||||
|
||||
sockaddr_storage remote_address;
|
||||
int clientFd;
|
||||
|
||||
bool event_read_hold = false;
|
||||
::event* readEvent = nullptr;
|
||||
|
||||
bool event_write_hold = false;
|
||||
::event* writeEvent = nullptr;
|
||||
threads::Mutex tickLock;
|
||||
|
||||
std::recursive_timed_mutex bufferLock;
|
||||
std::deque<pipes::buffer> write_queue;
|
||||
std::deque<pipes::buffer> read_queue;
|
||||
pipes::buffer read_buffer; /* buffer which contains fragments of decoded data, e.g. HTTP request. Access only within tickLock locked */
|
||||
FTType ftType = FTType::Unknown;
|
||||
|
||||
pipes::WebSocket ws_handler;
|
||||
pipes::SSL ssl_handler;
|
||||
bool https_upload_init = false;
|
||||
bool http_init = false; /* general flag if the HTTP header has been parsed */
|
||||
|
||||
std::shared_ptr<ConnectedClient> client;
|
||||
std::shared_ptr<file::FileTransfereKey> pendingKey = nullptr;
|
||||
std::chrono::time_point<std::chrono::system_clock> last_io_action;
|
||||
std::chrono::time_point<std::chrono::system_clock> connect_timestamp;
|
||||
std::chrono::time_point<std::chrono::system_clock> finished_timestamp;
|
||||
|
||||
std::fstream* fstream = nullptr;
|
||||
size_t bytesHandled = 0;
|
||||
|
||||
ConnectionState state_connection = ConnectionState::C_CONNECTED;
|
||||
TransferState state_transfer = TransferState::T_INITIALIZE;
|
||||
|
||||
size_t availableBytes();
|
||||
std::string getBytes(size_t);
|
||||
std::string peekBytes(size_t);
|
||||
|
||||
std::mutex thread_flush_lock;
|
||||
std::thread thread_flush;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <src/server/file/LocalFileServer.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/std_unique_ptr.h>
|
||||
#include <pipes/buffer.h>
|
||||
#include "FileClient.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts::server;
|
||||
|
||||
void FileClient::sendRawMessage(const pipes::buffer_view &message) {
|
||||
lock_guard lock(this->bufferLock);
|
||||
this->write_queue.push_back(message.own_buffer());
|
||||
|
||||
if(!this->event_write_hold)
|
||||
event_add(this->writeEvent, nullptr);
|
||||
else {
|
||||
__asm__("nop");
|
||||
}
|
||||
}
|
||||
|
||||
void FileClient::handleMessageWrite(int fd, short, void *) {
|
||||
auto self = this->_this.lock();
|
||||
if(!self) return;
|
||||
|
||||
decltype(this->pendingKey) pending_key;
|
||||
{
|
||||
|
||||
lock_guard<threads::Mutex> l(this->tickLock);
|
||||
pending_key = this->pendingKey;
|
||||
}
|
||||
|
||||
unique_lock lock(this->bufferLock, defer_lock);
|
||||
if(!lock.try_lock_for(milliseconds(5))) {
|
||||
logWarning(LOG_FT, "{} Buffer lock locked, could not write data!", this->client_prefix());
|
||||
return; /* somebody else doing a action and will (hopefully) readd the event */
|
||||
}
|
||||
if(self->state_connection == C_DISCONNECTED) return;
|
||||
|
||||
auto used = this->used_bandwidth();
|
||||
if(pending_key) { //Delay the write <3 :P
|
||||
if(pending_key->max_bandwhidth >= 0 && used > pending_key->max_bandwhidth) {
|
||||
event_write_hold = true;
|
||||
logTrace(LOG_FT, "{} Exceeded bandwidth limit of {} bytes (Used {} bytes). Temporary skipping write event!", this->client_prefix(), pending_key->max_bandwhidth, this->used_bandwidth());
|
||||
return;
|
||||
}
|
||||
}
|
||||
event_write_hold = false;
|
||||
|
||||
pipes::buffer* buffer = nullptr;
|
||||
while(true) {
|
||||
if(this->write_queue.empty()) {
|
||||
lock.unlock(); /* unlock write buffer because we're ticking */
|
||||
if(pending_key && !pending_key->upload)
|
||||
this->handle->tickFileClient(_this.lock()); //We have to fill up again
|
||||
return;
|
||||
}
|
||||
|
||||
buffer = &this->write_queue.front();
|
||||
if(buffer->empty()) {
|
||||
this->write_queue.pop_front();
|
||||
buffer = nullptr;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ssize_t length = buffer->length();
|
||||
if(pending_key && pending_key->max_bandwhidth >= 0) {
|
||||
if(pending_key->max_bandwhidth < length && pending_key->max_bandwhidth > used) length = pending_key->max_bandwhidth - used;
|
||||
}
|
||||
|
||||
length = send(fd, buffer->data_ptr(), length, MSG_NOSIGNAL);
|
||||
//logTrace(LOG_FT, "{} Wrote {} bytes", this->client_prefix(), length);
|
||||
|
||||
if(length == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN) {
|
||||
event_add(this->writeEvent, nullptr);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
logError(LOG_FT, "{} Failed to write some data! ({}/{})", this->client_prefix(), errno, strerror(errno));
|
||||
lock.unlock();
|
||||
self->disconnect();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
*buffer = buffer->range(length);
|
||||
|
||||
auto entry = make_unique<BandwidthEntry>();
|
||||
entry->length = length ;
|
||||
entry->timestamp = system_clock::now();
|
||||
{
|
||||
lock_guard<recursive_mutex> b_lock(this->bandwidth_lock);
|
||||
this->bandwidth.push_back(move(entry));
|
||||
}
|
||||
}
|
||||
last_io_action = system_clock::now();
|
||||
|
||||
if(buffer->empty()) {
|
||||
this->write_queue.pop_front();
|
||||
buffer = nullptr;
|
||||
}
|
||||
|
||||
if(pending_key && this->bytesHandled != pending_key->size) {
|
||||
if(this->write_queue.size() < 4) this->handle->tickFileClient(_this.lock());
|
||||
}
|
||||
|
||||
if(!this->write_queue.empty()) {
|
||||
event_add(this->writeEvent, nullptr);
|
||||
}
|
||||
self.reset();
|
||||
}
|
||||
|
||||
void FileClient::handleMessageRead(int fd, short, void *) {
|
||||
auto self = this->_this.lock();
|
||||
if(self->state_connection != C_CONNECTED) return;
|
||||
|
||||
decltype(this->pendingKey) pending_key;
|
||||
{
|
||||
|
||||
lock_guard<threads::Mutex> l(this->tickLock);
|
||||
pending_key = this->pendingKey;
|
||||
}
|
||||
size_t buffer_length = 1024;
|
||||
if(pending_key && pending_key->max_bandwhidth >= 0) {
|
||||
auto used = this->used_bandwidth();
|
||||
if(used < pending_key->max_bandwhidth) {
|
||||
buffer_length = pending_key->max_bandwhidth - used;
|
||||
if(buffer_length > 1024) buffer_length = 1024;
|
||||
} else {
|
||||
logTrace(LOG_FT, "{} Exceeded bandwidth limit of {} bytes (Used {} bytes). Temporary removing read event!", this->client_prefix(), pending_key->max_bandwhidth, used);
|
||||
{
|
||||
lock_guard lock(this->bufferLock);
|
||||
if(this->readEvent)
|
||||
event_del_noblock(this->readEvent);
|
||||
}
|
||||
this->event_read_hold = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
pipes::buffer buffer(buffer_length);
|
||||
auto length = recv(fd, buffer.data_ptr(), buffer.length(), 0);
|
||||
if(length < 0){
|
||||
if(errno == EINTR || errno == EAGAIN)
|
||||
;//event_add(this->readEvent, nullptr);
|
||||
else {
|
||||
{
|
||||
lock_guard lock(this->bufferLock);
|
||||
if(this->readEvent)
|
||||
event_del_noblock(this->readEvent);
|
||||
}
|
||||
if(this->state_connection == C_CONNECTED) {
|
||||
logError(LOG_FT, "{} Failed to read some data! ({}/{})", this->client_prefix(), errno, strerror(errno));
|
||||
self->disconnect();
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if(length == 0){
|
||||
{
|
||||
lock_guard lock(this->bufferLock);
|
||||
if(this->readEvent)
|
||||
event_del_noblock(this->readEvent);
|
||||
}
|
||||
if(this->state_connection == C_CONNECTED) {
|
||||
if(this->state_transfer == T_TRANSFER)
|
||||
logWarning(LOG_FT, "{} Transfer hang up. Remote peer closed the connection.", this->client_prefix());
|
||||
else
|
||||
logMessage(LOG_FT, "{} Remote peer has closed the connection before initializing a transfer.", this->client_prefix());
|
||||
self->disconnect(seconds(3));
|
||||
}
|
||||
return;
|
||||
}
|
||||
last_io_action = system_clock::now();
|
||||
|
||||
buffer.resize(length);
|
||||
{
|
||||
lock_guard lock(this->bufferLock);
|
||||
if(self->state_connection != C_CONNECTED) return; /* drop the buffer because we're not connected anymore */
|
||||
|
||||
this->read_queue.push_back(std::move(buffer));
|
||||
}
|
||||
|
||||
this->handle->tickFileClient(_this.lock());
|
||||
|
||||
auto entry = make_unique<BandwidthEntry>();
|
||||
entry->length = length ;
|
||||
entry->timestamp = system_clock::now();
|
||||
//logTrace(LOG_FT, "{} Readed {} bytes", this->client_prefix(), length);
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(this->bandwidth_lock);
|
||||
this->bandwidth.push_back(move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
size_t FileClient::availableBytes() {
|
||||
lock_guard lock(this->bufferLock);
|
||||
size_t available = 0;
|
||||
for(const auto& buf : this->read_queue)
|
||||
available += buf.length();
|
||||
return available;
|
||||
}
|
||||
|
||||
std::string FileClient::peekBytes(size_t size) {
|
||||
ssize_t required = size;
|
||||
lock_guard lock(this->bufferLock);
|
||||
|
||||
string result;
|
||||
result.reserve(size);
|
||||
|
||||
for(pipes::buffer& buf : this->read_queue) {
|
||||
if(required <= 0) break;
|
||||
if(buf.length() > required) {
|
||||
result += string(buf.data_ptr<const char>(), required);
|
||||
required = 0;
|
||||
} else {
|
||||
result += string(buf.data_ptr<const char>(), buf.length());
|
||||
required -= buf.length();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string FileClient::getBytes(size_t size) {
|
||||
lock_guard lock(this->bufferLock);
|
||||
|
||||
string result;
|
||||
result.reserve(size);
|
||||
|
||||
while(!this->read_queue.empty()) {
|
||||
if(size <= 0) break;
|
||||
|
||||
auto& buf = this->read_queue.front();
|
||||
if(buf.length() > size) {
|
||||
result += string((char*) buf.pipes::buffer_view::data_ptr(), size);
|
||||
buf = buf.range(size);
|
||||
size = 0;
|
||||
} else {
|
||||
result += string((char*) buf.data_ptr(), buf.length());
|
||||
size -= buf.length();
|
||||
this->read_queue.pop_front();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -101,7 +101,7 @@ bool MusicClient::disconnect(const std::string &reason) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool server::MusicClient::notifyClientMoved(
|
||||
bool MusicClient::notifyClientMoved(
|
||||
const std::shared_ptr<ConnectedClient> &client,
|
||||
const std::shared_ptr<BasicChannel> &target_channel,
|
||||
ViewReasonId reason,
|
||||
|
||||
@@ -98,7 +98,7 @@ void MusicClient::replay_song(const shared_ptr<music::PlayableSong> &entry, cons
|
||||
auto info_list = serverInstance->databaseHelper()->queryDatabaseInfo(self->getServer(), {entry->getInvoker()});
|
||||
if(!info_list.empty()) {
|
||||
auto info = info_list.front();
|
||||
invoker = "[URL=client://0/" + info->uniqueId + "~WolverinDEV]" + info->lastName + "[/URL]";
|
||||
invoker = "[URL=client://0/" + info->client_unique_id + "~WolverinDEV]" + info->client_nickname + "[/URL]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "ChannelProvider.h"
|
||||
#include "../../MusicClient.h"
|
||||
#include "../../../../InstanceHandler.h"
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "../../../../../../music/providers/ffmpeg/FFMpegProvider.h"
|
||||
|
||||
|
||||
@@ -22,6 +21,7 @@ threads::Future<std::shared_ptr<::music::MusicPlayer>> ChannelProvider::createPl
|
||||
auto server = ((VirtualServer*) ptr_server)->ref();
|
||||
threads::Future<std::shared_ptr<::music::MusicPlayer>> future;
|
||||
|
||||
#if 0
|
||||
if(server) {
|
||||
std::thread([future, server, url, ptr_server]{
|
||||
auto f_server = serverInstance->getFileServer();
|
||||
@@ -116,7 +116,9 @@ threads::Future<std::shared_ptr<::music::MusicPlayer>> ChannelProvider::createPl
|
||||
} else {
|
||||
future.executionFailed("invalid bot");
|
||||
}
|
||||
|
||||
#else
|
||||
future.executionFailed("channel file playback is currently not supported");
|
||||
#endif
|
||||
|
||||
return future;
|
||||
}
|
||||
@@ -165,6 +167,7 @@ threads::Future<shared_ptr<UrlInfo>> ChannelProvider::query_info(const std::stri
|
||||
auto server = ((VirtualServer*) ptr_server)->ref();
|
||||
threads::Future<shared_ptr<UrlInfo>> future;
|
||||
|
||||
#if 0
|
||||
if(server) {
|
||||
std::thread([future, server, url, ptr_server]{
|
||||
auto f_server = serverInstance->getFileServer();
|
||||
@@ -264,6 +267,9 @@ threads::Future<shared_ptr<UrlInfo>> ChannelProvider::query_info(const std::stri
|
||||
future.executionFailed("invalid bot");
|
||||
}
|
||||
|
||||
#else
|
||||
future.executionFailed("channel file playback is currently not supported");
|
||||
#endif
|
||||
|
||||
return future;
|
||||
}
|
||||
@@ -475,6 +475,22 @@ bool QueryClient::handleMessage(const pipes::buffer_view& message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(auto non_escape{command.find_first_not_of('\r')}; non_escape == std::string::npos) {
|
||||
logTrace(LOG_QUERY, "[{}:{}] Got query idle command (\\r)", this->getLoggingPeerIp(), this->getPeerPort());
|
||||
CMD_RESET_IDLE; //if idle time over 5 min than connection drop
|
||||
return true;
|
||||
} else {
|
||||
command = command.substr(non_escape);
|
||||
}
|
||||
|
||||
if(auto non_escape{command.find_first_not_of('\n')}; non_escape == std::string::npos) {
|
||||
logTrace(LOG_QUERY, "[{}:{}] Got query idle command (\\n)", this->getLoggingPeerIp(), this->getPeerPort());
|
||||
CMD_RESET_IDLE; //if idle time over 5 min than connection drop
|
||||
return true;
|
||||
} else {
|
||||
command = command.substr(non_escape);
|
||||
}
|
||||
|
||||
if((uint8_t) command[0] == 255) {
|
||||
string commands{};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Properties.h"
|
||||
#include "query/Command.h"
|
||||
#include <algorithm>
|
||||
#include <zstd.h>
|
||||
#include <src/server/QueryServer.h>
|
||||
#include <src/VirtualServerManager.h>
|
||||
#include <src/InstanceHandler.h>
|
||||
@@ -11,6 +12,7 @@
|
||||
#include <src/ShutdownHelper.h>
|
||||
#include <ThreadPool/Timer.h>
|
||||
#include <numeric>
|
||||
#include "src/manager/ActionLogger.h"
|
||||
|
||||
#include "src/client/command_handler/helpers.h"
|
||||
|
||||
@@ -101,7 +103,7 @@ command_result QueryClient::handleCommand(Command& cmd) {
|
||||
case string_hash("bindinglist"):
|
||||
return this->handleCommandBindingList(cmd);
|
||||
case string_hash("serversnapshotdeploy"): {
|
||||
#if 1
|
||||
#if 0
|
||||
return this->handleCommandServerSnapshotDeploy(cmd);
|
||||
#else
|
||||
auto cmd_str = cmd.build();
|
||||
@@ -154,8 +156,10 @@ command_result QueryClient::handleCommandLogin(Command& cmd) {
|
||||
{
|
||||
threads::MutexLock lock(this->handle->loginLock);
|
||||
|
||||
if(!account)
|
||||
if(!account) {
|
||||
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::UNKNOWN_USER);
|
||||
return command_result{error::client_invalid_password, "username or password dose not match"};
|
||||
}
|
||||
|
||||
if (account->password != password) {
|
||||
if(!this->whitelisted) {
|
||||
@@ -168,6 +172,8 @@ command_result QueryClient::handleCommandLogin(Command& cmd) {
|
||||
return command_result{error::ban_flooding};
|
||||
}
|
||||
}
|
||||
|
||||
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::INVALID_PASSWORD);
|
||||
return command_result{error::client_invalid_password, "username or password dose not match"};
|
||||
}
|
||||
}
|
||||
@@ -242,12 +248,13 @@ command_result QueryClient::handleCommandLogin(Command& cmd) {
|
||||
this->properties()[property::CLIENT_TOTALCONNECTIONS]++;
|
||||
this->updateChannelClientProperties(true, true);
|
||||
|
||||
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::SUCCESS);
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
command_result QueryClient::handleCommandLogout(Command &) {
|
||||
CMD_RESET_IDLE;
|
||||
if(this->properties()[property::CLIENT_LOGIN_NAME].as<string>().empty()) return command_result{error::client_not_logged_in, "not logged in"};
|
||||
if(this->properties()[property::CLIENT_LOGIN_NAME].as<string>().empty()) return command_result{error::client_not_logged_in};
|
||||
this->properties()[property::CLIENT_LOGIN_NAME] = "";
|
||||
this->query_account = nullptr;
|
||||
|
||||
@@ -293,6 +300,7 @@ command_result QueryClient::handleCommandLogout(Command &) {
|
||||
}
|
||||
|
||||
this->updateChannelClientProperties(true, true);
|
||||
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), "", log::QueryAuthenticateResult::SUCCESS);
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
@@ -315,6 +323,7 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) {
|
||||
if(target && target->getState() != ServerState::ONLINE && target->getState() != ServerState::OFFLINE) return command_result{error::server_is_not_running};
|
||||
if(target == this->server) return command_result{error::ok};
|
||||
|
||||
auto old_server_id = this->getServerId();
|
||||
if(target) {
|
||||
if(this->query_account && this->query_account->bound_server > 0) {
|
||||
if(target->getServerId() != this->query_account->bound_server)
|
||||
@@ -362,7 +371,7 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) {
|
||||
this->update_cached_permissions();
|
||||
}
|
||||
this->updateChannelClientProperties(true, true);
|
||||
|
||||
serverInstance->action_logger()->query_logger.log_query_switch(std::dynamic_pointer_cast<QueryClient>(this->ref()), this->properties()[property::CLIENT_LOGIN_NAME].value(), old_server_id, this->getServerId());
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -632,6 +641,7 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) {
|
||||
|
||||
time_global = duration_cast<milliseconds>(end - start);
|
||||
}
|
||||
serverInstance->action_logger()->server_logger.log_server_create(server->getServerId(), this->ref(), log::ServerCreateReason::USER_ACTION);
|
||||
|
||||
Command res("");
|
||||
res["sid"] = server->getServerId();
|
||||
@@ -656,6 +666,7 @@ command_result QueryClient::handleCommandServerDelete(Command& cmd) {
|
||||
auto server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
|
||||
if(!server) return command_result{error::server_invalid_id, "invalid bounded server"};
|
||||
if(!serverInstance->getVoiceServerManager()->deleteServer(server)) return command_result{error::vs_critical};
|
||||
serverInstance->action_logger()->server_logger.log_server_delete(server->getServerId(), this->ref());
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -684,7 +695,9 @@ command_result QueryClient::handleCommandServerStart(Command& cmd) {
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_start_any, 1);
|
||||
|
||||
string err;
|
||||
if(!server->start(err)) return command_result{error::vs_critical, err};
|
||||
if(!server->start(err))
|
||||
return command_result{error::vs_critical, err};
|
||||
serverInstance->action_logger()->server_logger.log_server_start(server->getServerId(), this->ref());
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -712,6 +725,7 @@ command_result QueryClient::handleCommandServerStop(Command& cmd) {
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_stop_any, 1);
|
||||
|
||||
server->stop("server stopped", false);
|
||||
serverInstance->action_logger()->server_logger.log_server_stop(server->getServerId(), this->ref());
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -784,8 +798,8 @@ command_result QueryClient::handleCommandHostInfo(Command &) {
|
||||
res["connection_bandwidth_received_last_minute_total"] = std::accumulate(report_minute.connection_bytes_received.begin(), report_minute.connection_bytes_received.end(), (size_t) 0U);
|
||||
|
||||
|
||||
res["connection_filetransfer_bandwidth_sent"] = report_minute.file_bytes_sent;
|
||||
res["connection_filetransfer_bandwidth_received"] = report_minute.file_bytes_received;
|
||||
res["connection_filetransfer_bandwidth_sent"] = report_second.file_bytes_sent;
|
||||
res["connection_filetransfer_bandwidth_received"] = report_second.file_bytes_received;
|
||||
res["connection_filetransfer_bytes_sent_total"] = total_stats.file_bytes_sent;
|
||||
res["connection_filetransfer_bytes_received_total"] = total_stats.file_bytes_received;
|
||||
|
||||
@@ -821,6 +835,7 @@ command_result QueryClient::handleCommandBindingList(Command& cmd) {
|
||||
|
||||
//TODO with mapping!
|
||||
command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
|
||||
#if 0
|
||||
CMD_RESET_IDLE;
|
||||
|
||||
auto start = system_clock::now();
|
||||
@@ -846,10 +861,20 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
|
||||
auto str = cmd.build().substr(strlen("serversnapshotdeploy "));
|
||||
if(!ignore_hash) {
|
||||
auto buildHash = base64::encode(digest::sha1(str));
|
||||
if(buildHash != hash) return command_result{error::parameter_invalid, "Invalid hash (Expected: \"" + hash + "\", Got: \"" + buildHash + "\")"};
|
||||
if(buildHash != hash)
|
||||
return command_result{error::parameter_invalid, "Invalid hash (Expected: \"" + hash + "\", Got: \"" + buildHash + "\")"};
|
||||
}
|
||||
|
||||
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
|
||||
{
|
||||
auto instances = serverInstance->getVoiceServerManager()->serverInstances();
|
||||
if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server)
|
||||
return command_result{error::server_max_vs_reached, "You reached the via config.yml enabled virtual server limit."};
|
||||
|
||||
if(instances.size() >= 65535)
|
||||
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Software limit reached)"};
|
||||
}
|
||||
|
||||
if(port == 0)
|
||||
port = serverInstance->getVoiceServerManager()->next_available_port(host);
|
||||
auto result = serverInstance->getVoiceServerManager()->createServerFromSnapshot(this->server, host, port, cmd, error);
|
||||
@@ -874,36 +899,53 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
|
||||
res["error_start"] = "";
|
||||
res["started"] = true;
|
||||
}
|
||||
serverInstance->action_logger()->server_logger.log_server_create(result->getServerId(), this->ref(), log::ServerCreateReason::SNAPSHOT_DEPLOY);
|
||||
}
|
||||
|
||||
res["time"] = duration_cast<milliseconds>(end - start).count();
|
||||
this->sendCommand(res);
|
||||
return command_result{error::ok};
|
||||
#else
|
||||
return command_result{error::command_not_found};
|
||||
#endif
|
||||
}
|
||||
|
||||
command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) {
|
||||
CMD_RESET_IDLE;
|
||||
|
||||
if(this->server) {
|
||||
return command_result{error::not_implemented};
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||
//host = this->server->properties()[property::VIRTUALSERVER_HOST].as<string>();
|
||||
//port = this->server->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
|
||||
} else {
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||
}
|
||||
|
||||
std::string error{};
|
||||
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
|
||||
//TODO: Create a server if no exists
|
||||
server_create_lock.unlock();
|
||||
auto server = this->server;
|
||||
auto result = serverInstance->getVoiceServerManager()->deploy_snapshot(error, server, command);
|
||||
|
||||
//TODO: Stop the server completely
|
||||
if(!serverInstance->getVoiceServerManager()->deploy_snapshot(error, 111, command)) {
|
||||
//TODO: Delete server is it was new
|
||||
return command_result{error::vs_critical, error};
|
||||
using SnapshotDeployResult = VirtualServerManager::SnapshotDeployResult;
|
||||
switch (result) {
|
||||
case SnapshotDeployResult::SUCCESS:
|
||||
break;
|
||||
|
||||
case SnapshotDeployResult::REACHED_SERVER_ID_LIMIT:
|
||||
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Server ID limit reached)"};
|
||||
|
||||
case SnapshotDeployResult::REACHED_SOFTWARE_SERVER_LIMIT:
|
||||
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Software limit reached)"};
|
||||
|
||||
case SnapshotDeployResult::REACHED_CONFIG_SERVER_LIMIT:
|
||||
return command_result{error::server_max_vs_reached, "You reached the via config.yml enabled virtual server limit."};
|
||||
|
||||
case SnapshotDeployResult::CUSTOM_ERROR:
|
||||
return command_result{error::vs_critical, error};
|
||||
}
|
||||
|
||||
ts::command_builder notify{""};
|
||||
notify.put_unchecked(0, "virtualserver_port", server->properties()[property::VIRTUALSERVER_PORT].value());
|
||||
notify.put_unchecked(0, "sid", server->getServerId());
|
||||
this->sendCommand(notify, false);
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -912,22 +954,50 @@ command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) {
|
||||
CMD_RESET_IDLE;
|
||||
CMD_REQ_SERVER;
|
||||
|
||||
Command result("");
|
||||
Command snapshot_command("");
|
||||
string error;
|
||||
|
||||
int version = cmd[0].has("version") ? cmd[0]["version"] : -1;
|
||||
if(version == -1 && (cmd.hasParm("lagacy") || cmd.hasParm("legacy")))
|
||||
if(version == -1 && (cmd.hasParm("lagacy") || cmd.hasParm("legacy"))) {
|
||||
version = 0;
|
||||
if(!serverInstance->getVoiceServerManager()->createServerSnapshot(result, this->server, version, error))
|
||||
}
|
||||
|
||||
if(!serverInstance->getVoiceServerManager()->createServerSnapshot(snapshot_command, this->server, version, error)) {
|
||||
return command_result{error::vs_critical, error};
|
||||
}
|
||||
|
||||
string data = result.build();
|
||||
auto buildHash = base64::encode(digest::sha1(data));
|
||||
if(version == -1 || version >= 3) {
|
||||
auto build_version = snapshot_command[0]["snapshot_version"].as<int>();
|
||||
snapshot_command.pop_bulk();
|
||||
|
||||
result.push_bulk_front();
|
||||
result[0]["hash"] = buildHash;
|
||||
auto snapshot_data = snapshot_command.build();
|
||||
auto max_compressed_size = ZSTD_compressBound(snapshot_data.size());
|
||||
if(ZSTD_isError(max_compressed_size)) {
|
||||
return command_result{error::vs_critical, "failed to calculate compressed size: " + std::string{ZSTD_getErrorName(max_compressed_size)}};
|
||||
}
|
||||
|
||||
this->sendCommand(result);
|
||||
std::string buffer{};
|
||||
buffer.resize(max_compressed_size);
|
||||
|
||||
auto compressed_size = ZSTD_compress(buffer.data(), buffer.size(), snapshot_data.data(), snapshot_data.length(), 1);
|
||||
if(ZSTD_isError(compressed_size)) {
|
||||
return command_result{error::vs_critical, "failed to compressed snapshot: " + std::string{ZSTD_getErrorName(compressed_size)}};
|
||||
}
|
||||
|
||||
ts::command_builder result{""};
|
||||
result.bulk(0).reserve(100 + compressed_size * 4/3);
|
||||
result.put_unchecked(0, "snapshot_version", build_version);
|
||||
result.put_unchecked(0, "data", base64::encode(std::string_view{buffer.data(), compressed_size}));
|
||||
this->sendCommand(result, false);
|
||||
} else {
|
||||
auto data = snapshot_command.build();
|
||||
auto buildHash = base64::encode(digest::sha1(data));
|
||||
|
||||
snapshot_command.push_bulk_front();
|
||||
snapshot_command[0]["hash"] = buildHash;
|
||||
|
||||
this->sendCommand(snapshot_command);
|
||||
}
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
//
|
||||
// Created by WolverinDEV on 29/07/2020.
|
||||
//
|
||||
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/base64.h>
|
||||
#include <misc/digest.h>
|
||||
#include <src/InstanceHandler.h>
|
||||
#include <misc/endianness.h>
|
||||
#include "CryptSetupHandler.h"
|
||||
#include "./VoiceClientConnection.h"
|
||||
|
||||
using namespace ts;
|
||||
using namespace ts::connection;
|
||||
using namespace ts::server::server::udp;
|
||||
|
||||
inline void generate_random(uint8_t *destination, size_t length) {
|
||||
while(length-- > 0)
|
||||
*(destination++) = (uint8_t) rand();
|
||||
}
|
||||
|
||||
CryptSetupHandler::CryptSetupHandler(VoiceClientConnection *connection) : connection{connection} { }
|
||||
|
||||
CryptSetupHandler::CommandHandleResult CryptSetupHandler::handle_command(const std::string_view &payload) {
|
||||
std::variant<ts::command_result, CommandHandleResult>(CryptSetupHandler::*command_handler)(const ts::command_parser&) = nullptr;
|
||||
|
||||
if(payload.starts_with("clientinitiv"))
|
||||
command_handler = &CryptSetupHandler::handleCommandClientInitIv;
|
||||
else if(payload.starts_with("clientek"))
|
||||
command_handler = &CryptSetupHandler::handleCommandClientEk;
|
||||
else if(payload.starts_with("clientinit"))
|
||||
command_handler = &CryptSetupHandler::handleCommandClientInit;
|
||||
|
||||
if(!command_handler)
|
||||
return CommandHandleResult::PASS_THROUGH;
|
||||
|
||||
this->last_command_ = std::chrono::system_clock::now();
|
||||
|
||||
ts::command_parser parser{payload};
|
||||
try {
|
||||
std::unique_lock cmd_lock{this->command_lock};
|
||||
auto result = (this->*command_handler)(parser);
|
||||
|
||||
CommandHandleResult handle_result;
|
||||
if(std::holds_alternative<CommandHandleResult>(result)) {
|
||||
handle_result = std::get<CommandHandleResult>(result);
|
||||
} else {
|
||||
auto cmd_result = std::move(std::get<ts::command_result>(result));
|
||||
|
||||
ts::command_builder notify{"error"};
|
||||
cmd_result.build_error_response(notify, "id");
|
||||
|
||||
if(parser.has_key("return_code"))
|
||||
notify.put_unchecked(0, "return_code", parser.value("return_code"));
|
||||
|
||||
this->connection->send_command(notify.build(), false, nullptr);
|
||||
|
||||
handle_result = cmd_result.has_error() ? CommandHandleResult::CLOSE_CONNECTION : CommandHandleResult::CONSUME_COMMAND;
|
||||
cmd_result.release_data();
|
||||
}
|
||||
return handle_result;
|
||||
} catch (std::exception& ex) {
|
||||
debugMessage(this->connection->virtual_server_id(), "{} Failed to handle connection command: {}. Closing connection.", this->connection->log_prefix(), ex.what());
|
||||
return CommandHandleResult::CLOSE_CONNECTION;
|
||||
}
|
||||
}
|
||||
|
||||
CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(const ts::command_parser &cmd) {
|
||||
auto client = this->connection->getCurrentClient();
|
||||
assert(client);
|
||||
|
||||
std::unique_lock state_lock{client->state_lock};
|
||||
if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */
|
||||
auto lastPingResponse = std::max(this->connection->ping_handler().last_ping_response(), this->connection->ping_handler().last_command_acknowledged());
|
||||
if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) {
|
||||
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
|
||||
this->connection->log_prefix(),
|
||||
duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - lastPingResponse).count()
|
||||
);
|
||||
return ts::command_result{error::ok};
|
||||
} else if(!config::voice::allow_session_reinitialize) {
|
||||
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings",
|
||||
this->connection->log_prefix(),
|
||||
duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - lastPingResponse).count()
|
||||
);
|
||||
return ts::command_result{error::ok};
|
||||
}
|
||||
logMessage(this->connection->virtual_server_id(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt",
|
||||
this->connection->log_prefix(),
|
||||
duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - lastPingResponse).count()
|
||||
);
|
||||
|
||||
state_lock.unlock();
|
||||
|
||||
{
|
||||
std::unique_lock server_channel_lock(client->server->get_channel_tree_lock()); /* we cant get moved if this is locked! */
|
||||
if(client->currentChannel)
|
||||
client->server->client_move(client->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock);
|
||||
}
|
||||
|
||||
client->finalDisconnect();
|
||||
state_lock.lock();
|
||||
} else if(client->state >= ConnectionState::DISCONNECTING) {
|
||||
state_lock.unlock();
|
||||
std::shared_lock disconnect_finish{client->finalDisconnectLock}; /* await until the last disconnect has been processed */
|
||||
state_lock.lock();
|
||||
client->state = ConnectionState::INIT_HIGH;
|
||||
} else if(client->state == ConnectionState::INIT_HIGH) {
|
||||
logTrace(client->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX_(client));
|
||||
return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */
|
||||
} else {
|
||||
client->state = ConnectionState::INIT_HIGH;
|
||||
}
|
||||
state_lock.unlock();
|
||||
|
||||
this->connection->reset();
|
||||
this->connection->packet_decoder().register_initiv_packet();
|
||||
this->connection->packet_statistics().reset_offsets();
|
||||
|
||||
bool use_teaspeak = cmd.has_switch("teaspeak");
|
||||
if(use_teaspeak ? !config::server::clients::teaspeak : !config::server::clients::teamspeak)
|
||||
return command_result{error::client_type_is_not_allowed};
|
||||
|
||||
if(use_teaspeak) {
|
||||
debugMessage(this->connection->virtual_server_id(), "{} Client using TeaSpeak.", this->connection->log_prefix());
|
||||
client->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_TEASPEAK;
|
||||
}
|
||||
|
||||
/* normal TeamSpeak handling */
|
||||
this->seed_client = base64::decode(cmd.value("alpha"));
|
||||
if(this->seed_client.length() != 10)
|
||||
return ts::command_result{error::parameter_invalid, "alpha"};
|
||||
|
||||
std::string clientOmega = base64::decode(cmd.value("omega")); //The identity public key
|
||||
std::string ip = cmd.value("ip");
|
||||
bool ot = cmd.has_key("ot") && cmd.value_as<bool>("ot");
|
||||
|
||||
{
|
||||
this->remote_key = std::shared_ptr<ecc_key>(new ecc_key{}, [](ecc_key* key){
|
||||
if(!key) return;
|
||||
ecc_free(key);
|
||||
delete key;
|
||||
});
|
||||
|
||||
auto state = ecc_import((const unsigned char *) clientOmega.data(), clientOmega.length(), &*this->remote_key);
|
||||
if(state != CRYPT_OK) {
|
||||
this->remote_key = nullptr;
|
||||
return ts::command_result{error::client_could_not_validate_identity};
|
||||
}
|
||||
|
||||
client->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd.value("omega")));
|
||||
}
|
||||
|
||||
this->new_protocol = !use_teaspeak && ot && config::experimental_31 && (this->client_protocol_time_ >= 173265950ULL || this->client_protocol_time_ == (uint32_t) 5680278000ULL);
|
||||
|
||||
{
|
||||
size_t server_seed_length = this->new_protocol ? 54 : 10;
|
||||
|
||||
char beta[server_seed_length];
|
||||
generate_random((uint8_t *) beta, server_seed_length);
|
||||
|
||||
this->seed_server = std::string{beta, server_seed_length};
|
||||
}
|
||||
|
||||
auto server_public_key = client->getServer()->publicServerKey();
|
||||
if(server_public_key.empty()) {
|
||||
return ts::command_result{error::vs_critical, "failed to export server public key"};
|
||||
}
|
||||
|
||||
if(this->new_protocol) {
|
||||
//Pre setup
|
||||
//Generate chain
|
||||
debugMessage(this->connection->virtual_server_id(), "{} Got client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_);
|
||||
|
||||
this->chain_data = serverInstance->getTeamSpeakLicense()->license();
|
||||
this->chain_data->chain->addEphemeralEntry();
|
||||
|
||||
auto crypto_chain = this->chain_data->chain->exportChain();
|
||||
auto crypto_chain_hash = digest::sha256(crypto_chain);
|
||||
|
||||
size_t sign_buffer_size{128};
|
||||
char sign_buffer[sign_buffer_size];
|
||||
|
||||
prng_state prng_state{};
|
||||
memset(&prng_state, 0, sizeof(prng_state));
|
||||
|
||||
auto sign_result = ecc_sign_hash(
|
||||
(u_char*) crypto_chain_hash.data(), crypto_chain_hash.length(),
|
||||
(u_char*) sign_buffer, &sign_buffer_size,
|
||||
&prng_state, find_prng("sprng"),
|
||||
client->getServer()->serverKey());
|
||||
|
||||
if(sign_result != CRYPT_OK) {
|
||||
return ts::command_result{error::vs_critical, "failed to sign crypto chain"};
|
||||
}
|
||||
|
||||
ts::command_builder answer{"initivexpand2"};
|
||||
answer.put_unchecked(0, "time", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
|
||||
answer.put_unchecked(0, "l", base64::encode(crypto_chain));
|
||||
answer.put_unchecked(0, "beta", base64::encode(this->seed_server));
|
||||
answer.put_unchecked(0, "omega", server_public_key);
|
||||
answer.put_unchecked(0, "proof", base64::encode((const char*) sign_buffer, sign_buffer_size));
|
||||
answer.put_unchecked(0, "tvd", "");
|
||||
answer.put_unchecked(0, "root", base64::encode((char*) this->chain_data->public_key, 32));
|
||||
answer.put_unchecked(0, "ot", "1");
|
||||
|
||||
this->connection->send_command(answer.build(), false, nullptr);
|
||||
client->handshake.state = SpeakingClient::HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */
|
||||
|
||||
return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */
|
||||
} else {
|
||||
debugMessage(this->connection->virtual_server_id(), "{} Got non client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_, this->client_protocol_time_);
|
||||
|
||||
{
|
||||
ts::command_builder answer{"initivexpand"};
|
||||
answer.put_unchecked(0, "alpha", base64::encode(this->seed_client));
|
||||
answer.put_unchecked(0, "beta", base64::encode(this->seed_server));
|
||||
answer.put_unchecked(0, "omega", server_public_key);
|
||||
|
||||
if(use_teaspeak) {
|
||||
answer.put_unchecked(0, "teaspeak", "1");
|
||||
client->handshake.state = SpeakingClient::HandshakeState::BEGIN; /* we need to start the handshake */
|
||||
} else {
|
||||
client->handshake.state = SpeakingClient::HandshakeState::SUCCEEDED; /* we're using the provided identity as identity */
|
||||
}
|
||||
|
||||
this->connection->send_command(answer.build(), false, nullptr);
|
||||
this->connection->packet_encoder().encrypt_pending_packets();
|
||||
}
|
||||
|
||||
std::string error;
|
||||
if(!this->connection->getCryptHandler()->setupSharedSecret(this->seed_client, this->seed_server, &*this->remote_key, client->getServer()->serverKey(), error)){
|
||||
logError(this->connection->virtual_server_id(), "{} Failed to calculate shared secret {}. Dropping client.",
|
||||
this->connection->log_prefix(), error);
|
||||
return ts::command_result{error::vs_critical};
|
||||
}
|
||||
|
||||
return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */
|
||||
}
|
||||
}
|
||||
|
||||
CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientEk(const ts::command_parser &cmd) {
|
||||
debugMessage(this->connection->virtual_server_id(), "{} Got client ek!", this->connection->log_prefix());
|
||||
|
||||
if(!this->chain_data || !this->chain_data->chain) {
|
||||
return ts::command_result{error::vs_critical, "missing chain data"};
|
||||
}
|
||||
|
||||
auto client_key = base64::decode(cmd.value("ek"));
|
||||
auto private_key = this->chain_data->chain->generatePrivateKey(this->chain_data->root_key, this->chain_data->root_index);
|
||||
|
||||
this->connection->getCryptHandler()->setupSharedSecretNew(this->seed_client, this->seed_server, (char*) private_key.data(), client_key.data());
|
||||
this->connection->packet_encoder().acknowledge_manager().reset();
|
||||
|
||||
{
|
||||
char buffer[2];
|
||||
le2be16(1, buffer);
|
||||
|
||||
auto pflags = protocol::PacketFlag::NewProtocol;
|
||||
this->connection->send_packet(protocol::PacketType::ACK, (protocol::PacketFlag::PacketFlag) pflags, buffer, 2);
|
||||
//Send the encrypted acknowledge (most the times the second packet; If not we're going into the resend loop)
|
||||
//We cant use the send_packet_acknowledge function since it sends the acknowledge unencrypted
|
||||
}
|
||||
|
||||
return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */
|
||||
}
|
||||
|
||||
CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInit(const ts::command_parser &) {
|
||||
/* the client must have received everything */
|
||||
this->connection->packet_encoder().acknowledge_manager().reset();
|
||||
this->seed_client.clear();
|
||||
this->seed_server.clear();
|
||||
this->chain_data = nullptr;
|
||||
|
||||
return CommandHandleResult::PASS_THROUGH;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <query/command3.h>
|
||||
#include <Error.h>
|
||||
#include <src/lincense/TeamSpeakLicense.h>
|
||||
#include <tomcrypt.h>
|
||||
#include <variant>
|
||||
|
||||
namespace ts::connection {
|
||||
class VoiceClientConnection;
|
||||
}
|
||||
|
||||
namespace ts::server::server::udp {
|
||||
class CryptSetupHandler {
|
||||
public:
|
||||
enum struct CommandHandleResult {
|
||||
CONSUME_COMMAND,
|
||||
CLOSE_CONNECTION,
|
||||
PASS_THROUGH
|
||||
};
|
||||
using CommandResult = std::variant<ts::command_result, CommandHandleResult>;
|
||||
|
||||
explicit CryptSetupHandler(connection::VoiceClientConnection*);
|
||||
|
||||
[[nodiscard]] inline const auto& identity_key() const { return this->remote_key; }
|
||||
|
||||
void set_client_protocol_time(uint32_t time) { this->client_protocol_time_ = time; }
|
||||
[[nodiscard]] inline auto client_protocol_time() const { return this->client_protocol_time_; }
|
||||
|
||||
[[nodiscard]] inline auto last_handled_command() const { return this->last_command_; }
|
||||
|
||||
/* Attention this method gets from the voice IO thread. It's not thread save! */
|
||||
[[nodiscard]] CommandHandleResult handle_command(const std::string_view& /* command */);
|
||||
private:
|
||||
connection::VoiceClientConnection* connection;
|
||||
|
||||
std::chrono::system_clock::time_point last_command_{};
|
||||
|
||||
std::mutex command_lock{};
|
||||
|
||||
bool new_protocol{false};
|
||||
uint32_t client_protocol_time_{0};
|
||||
|
||||
std::string seed_client{}; /* alpha */
|
||||
std::string seed_server{}; /* beta */
|
||||
|
||||
std::shared_ptr<LicenseChainData> chain_data{};
|
||||
std::shared_ptr<ecc_key> remote_key{};
|
||||
|
||||
CommandResult handleCommandClientInitIv(const ts::command_parser& /* command */);
|
||||
CommandResult handleCommandClientEk(const ts::command_parser& /* command */);
|
||||
|
||||
CommandResult handleCommandClientInit(const ts::command_parser& /* command */);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
//
|
||||
// Created by WolverinDEV on 10/03/2020.
|
||||
//
|
||||
|
||||
#include "PacketDecoder.h"
|
||||
|
||||
#include <protocol/buffers.h>
|
||||
#include <protocol/AcknowledgeManager.h>
|
||||
#include <protocol/CompressionHandler.h>
|
||||
#include <protocol/CryptHandler.h>
|
||||
|
||||
#include "../../ConnectionStatistics.h"
|
||||
|
||||
using namespace ts;
|
||||
using namespace ts::protocol;
|
||||
using namespace ts::connection;
|
||||
using namespace ts::server::server::udp;
|
||||
|
||||
ReassembledCommand *ReassembledCommand::allocate(size_t size) {
|
||||
auto instance = (ReassembledCommand*) malloc(sizeof(ReassembledCommand) + size);
|
||||
instance->length_ = size;
|
||||
instance->capacity_ = size;
|
||||
instance->next_command = nullptr;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ReassembledCommand::free(ReassembledCommand *command) {
|
||||
::free(command);
|
||||
}
|
||||
|
||||
PacketDecoder::PacketDecoder(ts::connection::CryptHandler *crypt_handler)
|
||||
: crypt_handler_{crypt_handler} {
|
||||
memtrack::allocated<PacketDecoder>(this);
|
||||
}
|
||||
|
||||
PacketDecoder::~PacketDecoder() {
|
||||
memtrack::freed<PacketDecoder>(this);
|
||||
this->reset();
|
||||
}
|
||||
|
||||
void PacketDecoder::reset() {
|
||||
std::lock_guard buffer_lock(this->packet_buffer_lock);
|
||||
for(auto& buffer : this->_command_fragment_buffers)
|
||||
buffer.reset();
|
||||
}
|
||||
|
||||
PacketProcessResult PacketDecoder::process_incoming_data(ClientPacketParser &packet_parser, std::string& error) {
|
||||
#ifdef FUZZING_TESTING_INCOMMING
|
||||
if(rand() % 100 < 20)
|
||||
return PacketProcessResult::FUZZ_DROPPED;
|
||||
|
||||
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
|
||||
if (this->client->state == ConnectionState::CONNECTED) {
|
||||
#endif
|
||||
if ((rand() % FUZZING_TESTING_DROP_MAX) < FUZZING_TESTING_DROP) {
|
||||
debugMessage(this->client->getServerId(), "{}[FUZZING] Dropping incoming packet of length {}", CLIENT_STR_LOG_PREFIX_(this->client), buffer.length());
|
||||
return;
|
||||
}
|
||||
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
auto result = this->decode_incoming_packet(error, packet_parser);
|
||||
if(result != PacketProcessResult::SUCCESS)
|
||||
return result;
|
||||
|
||||
#ifdef LOG_INCOMPING_PACKET_FRAGMENTS
|
||||
debugMessage(lstream << CLIENT_LOG_PREFIX << "Recived packet. PacketId: " << packet->packetId() << " PacketType: " << packet->type().name() << " Flags: " << packet->flags() << " - " << packet->data() << endl);
|
||||
#endif
|
||||
auto is_command = packet_parser.type() == protocol::COMMAND || packet_parser.type() == protocol::COMMAND_LOW;
|
||||
if(is_command) {
|
||||
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(packet_parser.type())];
|
||||
CommandFragment fragment_entry{
|
||||
packet_parser.packet_id(),
|
||||
packet_parser.estimated_generation(),
|
||||
|
||||
packet_parser.flags(),
|
||||
(uint32_t) packet_parser.payload_length(),
|
||||
packet_parser.payload().own_buffer()
|
||||
};
|
||||
|
||||
std::unique_lock queue_lock(fragment_buffer.buffer_lock);
|
||||
|
||||
auto insert_result = fragment_buffer.insert_index2(packet_parser.full_packet_id(), std::move(fragment_entry));
|
||||
if(insert_result != 0) {
|
||||
queue_lock.unlock();
|
||||
|
||||
error = "pid: " + std::to_string(packet_parser.packet_id()) + ", ";
|
||||
error += "bidx: " + std::to_string(fragment_buffer.current_index()) + ", ";
|
||||
error += "bcap: " + std::to_string(fragment_buffer.capacity());
|
||||
|
||||
if(insert_result == -2) {
|
||||
return PacketProcessResult::DUPLICATED_PACKET;
|
||||
} else if(insert_result == -1) {
|
||||
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
|
||||
return PacketProcessResult::BUFFER_UNDERFLOW;
|
||||
} else if(insert_result == 1) {
|
||||
return PacketProcessResult::BUFFER_OVERFLOW;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return PacketProcessResult::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
|
||||
|
||||
ReassembledCommand* command{nullptr};
|
||||
CommandReassembleResult assemble_result;
|
||||
do {
|
||||
if(!queue_lock.owns_lock())
|
||||
queue_lock.lock();
|
||||
|
||||
assemble_result = this->try_reassemble_ordered_packet(fragment_buffer, queue_lock, command);
|
||||
|
||||
if(assemble_result == CommandReassembleResult::SUCCESS || assemble_result == CommandReassembleResult::MORE_COMMANDS_PENDING)
|
||||
this->callback_decoded_command(this->callback_argument, command);
|
||||
|
||||
if(command) {
|
||||
/* ownership hasn't transferred */
|
||||
ReassembledCommand::free(command);
|
||||
command = nullptr;
|
||||
}
|
||||
|
||||
switch (assemble_result) {
|
||||
case CommandReassembleResult::NO_COMMANDS_PENDING:
|
||||
case CommandReassembleResult::SUCCESS:
|
||||
case CommandReassembleResult::MORE_COMMANDS_PENDING:
|
||||
break;
|
||||
|
||||
case CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG:
|
||||
return PacketProcessResult::COMMAND_BUFFER_OVERFLOW;
|
||||
|
||||
case CommandReassembleResult::COMMAND_TOO_LARGE:
|
||||
return PacketProcessResult::COMMAND_TOO_LARGE;
|
||||
|
||||
case CommandReassembleResult::COMMAND_DECOMPRESS_FAILED:
|
||||
return PacketProcessResult::COMMAND_DECOMPRESS_FAILED;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
} while(assemble_result == CommandReassembleResult::MORE_COMMANDS_PENDING);
|
||||
} else {
|
||||
this->callback_decoded_packet(this->callback_argument, packet_parser);
|
||||
}
|
||||
|
||||
return PacketProcessResult::SUCCESS;
|
||||
}
|
||||
|
||||
PacketProcessResult PacketDecoder::decode_incoming_packet(std::string& error, ClientPacketParser &packet_parser) {
|
||||
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
|
||||
|
||||
auto& generation_estimator = this->incoming_generation_estimators[packet_parser.type()];
|
||||
{
|
||||
std::lock_guard glock{this->incoming_generation_estimator_lock};
|
||||
packet_parser.set_estimated_generation(generation_estimator.visit_packet(packet_parser.packet_id()));
|
||||
}
|
||||
|
||||
/* decrypt the packet if needed */
|
||||
if(packet_parser.is_encrypted()) {
|
||||
CryptHandler::key_t crypt_key{};
|
||||
CryptHandler::nonce_t crypt_nonce{};
|
||||
|
||||
auto data = (uint8_t*) packet_parser.mutable_data_ptr();
|
||||
bool use_default_key{!this->crypt_handler_->encryption_initialized()}, decrypt_result;
|
||||
|
||||
decrypt_packet:
|
||||
if(use_default_key) {
|
||||
crypt_key = CryptHandler::kDefaultKey;
|
||||
crypt_nonce = CryptHandler::kDefaultNonce;
|
||||
} else {
|
||||
if(!this->crypt_handler_->generate_key_nonce(true, packet_parser.type(), packet_parser.packet_id(), packet_parser.estimated_generation(), crypt_key, crypt_nonce))
|
||||
return PacketProcessResult::DECRYPT_KEY_GEN_FAILED;
|
||||
}
|
||||
|
||||
decrypt_result = this->crypt_handler_->decrypt(
|
||||
data + ClientPacketParser::kHeaderOffset, ClientPacketParser::kHeaderLength,
|
||||
data + ClientPacketParser::kPayloadOffset, packet_parser.payload_length(),
|
||||
data,
|
||||
crypt_key, crypt_nonce,
|
||||
error
|
||||
);
|
||||
|
||||
if(!decrypt_result) {
|
||||
if(packet_parser.packet_id() < 10 && packet_parser.estimated_generation() == 0) {
|
||||
if(use_default_key) {
|
||||
return PacketProcessResult::DECRYPT_FAILED;
|
||||
} else {
|
||||
use_default_key = true;
|
||||
goto decrypt_packet;
|
||||
}
|
||||
} else {
|
||||
return PacketProcessResult::DECRYPT_FAILED;
|
||||
}
|
||||
}
|
||||
packet_parser.set_decrypted();
|
||||
}
|
||||
|
||||
return PacketProcessResult::SUCCESS;
|
||||
}
|
||||
|
||||
bool PacketDecoder::verify_encryption(const pipes::buffer_view &buffer) {
|
||||
ClientPacketParser packet_parser{buffer};
|
||||
if(!packet_parser.valid() || !packet_parser.is_encrypted()) return false;
|
||||
|
||||
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
|
||||
return this->crypt_handler_->verify_encryption(buffer, packet_parser.packet_id(), this->incoming_generation_estimators[packet_parser.type()].generation());
|
||||
}
|
||||
|
||||
void PacketDecoder::register_initiv_packet() {
|
||||
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
|
||||
std::unique_lock buffer_lock(fragment_buffer.buffer_lock);
|
||||
fragment_buffer.set_full_index_to(1); /* the first packet (0) is already the clientinitiv packet */
|
||||
}
|
||||
|
||||
CommandReassembleResult PacketDecoder::try_reassemble_ordered_packet(
|
||||
command_fragment_buffer_t &buffer,
|
||||
std::unique_lock<std::mutex> &buffer_lock,
|
||||
ReassembledCommand *&assembled_command) {
|
||||
assert(buffer_lock.owns_lock());
|
||||
|
||||
if(!buffer.front_set())
|
||||
return CommandReassembleResult::NO_COMMANDS_PENDING;
|
||||
|
||||
uint8_t packet_flags{0};
|
||||
|
||||
std::unique_ptr<ReassembledCommand, void(*)(ReassembledCommand*)> rcommand{nullptr, ReassembledCommand::free};
|
||||
|
||||
/* lets find out if we've to reassemble the packet */
|
||||
auto& first_buffer = buffer.slot_value(0);
|
||||
if(first_buffer.packet_flags & PacketFlag::Fragmented) {
|
||||
uint16_t sequence_length{1};
|
||||
size_t total_payload_length{first_buffer.payload_length};
|
||||
do {
|
||||
if(sequence_length >= buffer.capacity())
|
||||
return CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG;
|
||||
|
||||
if(!buffer.slot_set(sequence_length))
|
||||
return CommandReassembleResult::NO_COMMANDS_PENDING; /* we need more packets */
|
||||
|
||||
auto& packet = buffer.slot_value(sequence_length++);
|
||||
total_payload_length += packet.payload_length;
|
||||
if(packet.packet_flags & PacketFlag::Fragmented) {
|
||||
/* yep we find the end */
|
||||
break;
|
||||
}
|
||||
} while(true);
|
||||
|
||||
/* ok we have all fragments lets reassemble */
|
||||
/*
|
||||
* Packet sequence could never be so long. If it is so then the data_length() returned an invalid value.
|
||||
* We're checking it here because we dont want to make a huge allocation
|
||||
*/
|
||||
assert(total_payload_length < 512 * 1024 * 1024);
|
||||
|
||||
rcommand.reset(ReassembledCommand::allocate(total_payload_length));
|
||||
char* packet_buffer_ptr = rcommand->command();
|
||||
size_t packet_count{0};
|
||||
|
||||
packet_flags = buffer.slot_value(0).packet_flags;
|
||||
while(packet_count < sequence_length) {
|
||||
auto fragment = buffer.pop_front();
|
||||
memcpy(packet_buffer_ptr, fragment.payload.data_ptr(), fragment.payload_length);
|
||||
|
||||
packet_buffer_ptr += fragment.payload_length;
|
||||
packet_count++;
|
||||
}
|
||||
|
||||
#ifndef _NDEBUG
|
||||
if((packet_buffer_ptr - 1) != &rcommand->command()[rcommand->length() - 1]) {
|
||||
logCritical(0,
|
||||
"Buffer over/underflow: packet_buffer_ptr != &packet_buffer[packet_buffer.length() - 1]; packet_buffer_ptr := {}; packet_buffer.end() := {}",
|
||||
(void*) packet_buffer_ptr,
|
||||
(void*) &rcommand->command()[rcommand->length() - 1]
|
||||
);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
auto packet = buffer.pop_front();
|
||||
packet_flags = packet.packet_flags;
|
||||
|
||||
rcommand.reset(ReassembledCommand::allocate(packet.payload_length));
|
||||
memcpy(rcommand->command(), packet.payload.data_ptr(), packet.payload_length);
|
||||
}
|
||||
|
||||
auto more_commands_pending = buffer.front_set(); /* set the more flag if we have more to process */
|
||||
buffer_lock.unlock();
|
||||
|
||||
if(packet_flags & PacketFlag::Compressed) {
|
||||
std::string error{};
|
||||
|
||||
auto compressed_command = std::move(rcommand);
|
||||
auto decompressed_size = compression::qlz_decompressed_size(compressed_command->command(), compressed_command->length());
|
||||
if(decompressed_size > 64 * 1024 * 1024)
|
||||
return CommandReassembleResult::COMMAND_TOO_LARGE;
|
||||
|
||||
rcommand.reset(ReassembledCommand::allocate(decompressed_size));
|
||||
if(!compression::qlz_decompress_payload(compressed_command->command(), rcommand->command(), &decompressed_size))
|
||||
return CommandReassembleResult::COMMAND_DECOMPRESS_FAILED;
|
||||
|
||||
rcommand->set_length(decompressed_size);
|
||||
}
|
||||
|
||||
assembled_command = rcommand.release();
|
||||
return more_commands_pending ? CommandReassembleResult::MORE_COMMANDS_PENDING : CommandReassembleResult::SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <protocol/Packet.h>
|
||||
#include <protocol/generation.h>
|
||||
#include <protocol/ringbuffer.h>
|
||||
|
||||
namespace ts::connection {
|
||||
class CryptHandler;
|
||||
class CompressionHandler;
|
||||
class AcknowledgeManager;
|
||||
}
|
||||
|
||||
namespace ts::stats {
|
||||
class ConnectionStatistics;
|
||||
}
|
||||
|
||||
namespace ts::server::server::udp {
|
||||
struct CommandFragment {
|
||||
uint16_t packet_id{0};
|
||||
uint16_t packet_generation{0};
|
||||
|
||||
uint8_t packet_flags{0};
|
||||
uint32_t payload_length : 24;
|
||||
pipes::buffer payload{};
|
||||
|
||||
CommandFragment() { this->payload_length = 0; }
|
||||
CommandFragment(uint16_t packetId, uint16_t packetGeneration, uint8_t packetFlags, uint32_t payloadLength, pipes::buffer payload)
|
||||
: packet_id{packetId}, packet_generation{packetGeneration}, packet_flags{packetFlags}, payload_length{payloadLength}, payload{std::move(payload)} {}
|
||||
|
||||
CommandFragment& operator=(const CommandFragment&) = default;
|
||||
CommandFragment(const CommandFragment& other) = default;
|
||||
CommandFragment(CommandFragment&&) = default;
|
||||
};
|
||||
static_assert(sizeof(CommandFragment) == 8 + sizeof(pipes::buffer));
|
||||
|
||||
struct ReassembledCommand {
|
||||
public:
|
||||
static ReassembledCommand* allocate(size_t /* command length */);
|
||||
static void free(ReassembledCommand* /* command */);
|
||||
|
||||
[[nodiscard]] inline size_t length() const { return this->length_; }
|
||||
inline void set_length(size_t length) { assert(this->capacity_ >= length); this->length_ = length; }
|
||||
|
||||
[[nodiscard]] inline size_t capacity() const { return this->capacity_; }
|
||||
|
||||
[[nodiscard]] inline const char* command() const { return (const char*) this + sizeof(ReassembledCommand); }
|
||||
[[nodiscard]] inline char* command() { return (char*) this + sizeof(ReassembledCommand); }
|
||||
|
||||
[[nodiscard]] inline std::string_view command_view() const { return std::string_view{this->command(), this->length()}; }
|
||||
|
||||
mutable ReassembledCommand* next_command; /* nullptr by default */
|
||||
private:
|
||||
explicit ReassembledCommand() = default;
|
||||
|
||||
size_t capacity_;
|
||||
size_t length_;
|
||||
};
|
||||
|
||||
enum struct PacketProcessResult {
|
||||
SUCCESS,
|
||||
UNKNOWN_ERROR,
|
||||
|
||||
FUZZ_DROPPED,
|
||||
|
||||
DUPLICATED_PACKET, /* error message contains debug properties */
|
||||
BUFFER_OVERFLOW, /* error message contains debug properties */
|
||||
BUFFER_UNDERFLOW, /* error message contains debug properties */
|
||||
|
||||
COMMAND_BUFFER_OVERFLOW, /* can cause a total connection drop */
|
||||
COMMAND_SEQUENCE_LENGTH_TOO_LONG, /* unrecoverable error */
|
||||
COMMAND_TOO_LARGE,
|
||||
COMMAND_DECOMPRESS_FAILED,
|
||||
|
||||
DECRYPT_KEY_GEN_FAILED,
|
||||
DECRYPT_FAILED, /* has custom message */
|
||||
};
|
||||
|
||||
enum struct CommandReassembleResult {
|
||||
SUCCESS,
|
||||
|
||||
MORE_COMMANDS_PENDING, /* equal with success */
|
||||
NO_COMMANDS_PENDING,
|
||||
|
||||
COMMAND_TOO_LARGE, /* this is a fatal error to the connection */
|
||||
COMMAND_DECOMPRESS_FAILED,
|
||||
|
||||
SEQUENCE_LENGTH_TOO_LONG /* unrecoverable error */
|
||||
};
|
||||
|
||||
class PacketDecoder {
|
||||
typedef protocol::FullPacketRingBuffer<CommandFragment, 32, CommandFragment> command_fragment_buffer_t;
|
||||
typedef std::array<command_fragment_buffer_t, 2> command_packet_reassembler;
|
||||
public:
|
||||
/* direct function calls are better optimized out */
|
||||
typedef void(*callback_decoded_packet_t)(void* /* cb argument */, const protocol::ClientPacketParser&);
|
||||
typedef void(*callback_decoded_command_t)(void* /* cb argument */, ReassembledCommand*& /* command */); /* must move the command, else it gets freed*/
|
||||
typedef void(*callback_send_acknowledge_t)(void* /* cb argument */, uint16_t /* packet id */, bool /* is command low */);
|
||||
|
||||
explicit PacketDecoder(connection::CryptHandler* /* crypt handler */);
|
||||
~PacketDecoder();
|
||||
|
||||
void reset();
|
||||
|
||||
bool verify_encryption(const pipes::buffer_view& /* full packet */);
|
||||
|
||||
/* true if commands might be pending */
|
||||
PacketProcessResult process_incoming_data(protocol::ClientPacketParser &/* packet */, std::string& /* error detail */);
|
||||
void register_initiv_packet();
|
||||
|
||||
void* callback_argument{nullptr};
|
||||
callback_decoded_packet_t callback_decoded_packet{[](auto, auto&){}}; /* needs to be valid all the time! */
|
||||
callback_decoded_command_t callback_decoded_command{[](auto, auto&){}}; /* needs to be valid all the time! */
|
||||
callback_send_acknowledge_t callback_send_acknowledge{[](auto, auto, auto){}}; /* needs to be valid all the time! */
|
||||
private:
|
||||
connection::CryptHandler* crypt_handler_{nullptr};
|
||||
|
||||
spin_mutex incoming_generation_estimator_lock{};
|
||||
std::array<protocol::generation_estimator, 9> incoming_generation_estimators{}; /* implementation is thread save */
|
||||
|
||||
std::recursive_mutex packet_buffer_lock;
|
||||
command_packet_reassembler _command_fragment_buffers;
|
||||
|
||||
static inline uint8_t command_fragment_buffer_index(uint8_t packet_index) {
|
||||
return packet_index & 0x1U; /* use 0 for command and 1 for command low */
|
||||
}
|
||||
|
||||
PacketProcessResult decode_incoming_packet(std::string &error /* error */, protocol::ClientPacketParser &packet_parser/* packet */);
|
||||
CommandReassembleResult try_reassemble_ordered_packet(command_fragment_buffer_t& /* buffer */, std::unique_lock<std::mutex>& /* buffer lock */, ReassembledCommand*& /* command */);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
//
|
||||
// Created by WolverinDEV on 09/03/2020.
|
||||
//
|
||||
#include "PacketEncoder.h"
|
||||
|
||||
#include <protocol/buffers.h>
|
||||
#include <protocol/CompressionHandler.h>
|
||||
#include <protocol/CryptHandler.cpp>
|
||||
|
||||
using namespace ts;
|
||||
using namespace ts::server::server::udp;
|
||||
|
||||
PacketEncoder::PacketEncoder(ts::connection::CryptHandler *crypt_handler, client::PacketStatistics* pstats)
|
||||
: crypt_handler_{crypt_handler}, packet_statistics_{pstats} {
|
||||
|
||||
this->acknowledge_manager_.callback_data = this;
|
||||
this->acknowledge_manager_.destroy_packet = [](void* packet) {
|
||||
reinterpret_cast<OutgoingServerPacket*>(packet)->unref();
|
||||
};
|
||||
this->acknowledge_manager_.callback_resend_failed = [](void* this_ptr, const auto& entry) {
|
||||
auto encoder = reinterpret_cast<PacketEncoder*>(this_ptr);
|
||||
encoder->callback_resend_failed(encoder->callback_data, entry);
|
||||
};
|
||||
}
|
||||
|
||||
PacketEncoder::~PacketEncoder() {
|
||||
this->reset();
|
||||
}
|
||||
|
||||
void PacketEncoder::reset() {
|
||||
this->acknowledge_manager_.reset();
|
||||
|
||||
protocol::OutgoingServerPacket *whead, *rhead;
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
whead = std::exchange(this->encrypt_queue_head, nullptr);
|
||||
rhead = std::exchange(this->resend_queue_head, nullptr);
|
||||
|
||||
this->encrypt_queue_tail = &this->encrypt_queue_head;
|
||||
this->resend_queue_tail = &this->resend_queue_head;
|
||||
}
|
||||
|
||||
while(whead) {
|
||||
auto next = whead->next;
|
||||
whead->unref();
|
||||
whead = next;
|
||||
}
|
||||
|
||||
while(rhead) {
|
||||
auto next = rhead->next;
|
||||
rhead->unref();
|
||||
rhead = next;
|
||||
}
|
||||
}
|
||||
|
||||
void PacketEncoder::send_packet(ts::protocol::OutgoingServerPacket *packet) {
|
||||
uint32_t full_id;
|
||||
{
|
||||
std::lock_guard id_lock{this->packet_id_mutex};
|
||||
full_id = this->packet_id_manager.generate_full_id(packet->packet_type());
|
||||
}
|
||||
packet->set_packet_id(full_id & 0xFFFFU);
|
||||
packet->generation = full_id >> 16U;
|
||||
|
||||
{
|
||||
std::lock_guard qlock{this->write_queue_mutex};
|
||||
*this->encrypt_queue_tail = packet;
|
||||
this->encrypt_queue_tail = &packet->next;
|
||||
}
|
||||
|
||||
this->callback_request_write(this->callback_data);
|
||||
|
||||
auto category = stats::ConnectionStatistics::category::from_type(packet->packet_type());
|
||||
this->callback_connection_stats(this->callback_data, category, packet->packet_length() + 96); /* 96 for the UDP packet overhead */
|
||||
}
|
||||
|
||||
void PacketEncoder::send_packet(protocol::PacketType type, protocol::PacketFlag::PacketFlags flag, const void *payload, size_t payload_size) {
|
||||
auto packet = protocol::allocate_outgoing_packet(payload_size);
|
||||
|
||||
packet->type_and_flags = (uint8_t) type | (uint8_t) flag;
|
||||
memcpy(packet->payload, payload, payload_size);
|
||||
|
||||
this->send_packet(packet);
|
||||
}
|
||||
|
||||
void PacketEncoder::send_packet_acknowledge(uint16_t pid, bool low) {
|
||||
char buffer[2];
|
||||
le2be16(pid, buffer);
|
||||
|
||||
auto pflags = PacketFlag::Unencrypted | PacketFlag::NewProtocol;
|
||||
this->send_packet(low ? protocol::PacketType::ACK_LOW : protocol::PacketType::ACK, (PacketFlag::PacketFlag) pflags, buffer, 2);
|
||||
}
|
||||
|
||||
|
||||
#define MAX_COMMAND_PACKET_PAYLOAD_LENGTH (487)
|
||||
void PacketEncoder::send_command(const std::string_view &command, bool low, std::unique_ptr<threads::Future<bool>> ack_listener) {
|
||||
bool own_data_buffer{false};
|
||||
void* own_data_buffer_ptr; /* imutable! */
|
||||
|
||||
const char* data_buffer{command.data()};
|
||||
size_t data_length{command.length()};
|
||||
|
||||
uint8_t head_pflags{0};
|
||||
PacketType ptype{low ? PacketType::COMMAND_LOW : PacketType::COMMAND};
|
||||
protocol::OutgoingServerPacket *packets_head{nullptr};
|
||||
protocol::OutgoingServerPacket **packets_tail{&packets_head};
|
||||
|
||||
/* only compress "long" commands */
|
||||
if(command.size() > 100) {
|
||||
size_t max_compressed_payload_size = compression::qlz_compressed_size(command.data(), command.length());
|
||||
auto compressed_buffer = ::malloc(max_compressed_payload_size);
|
||||
|
||||
size_t compressed_size{max_compressed_payload_size};
|
||||
if(!compression::qlz_compress_payload(command.data(), command.length(), compressed_buffer, &compressed_size)) {
|
||||
logCritical(0, "Failed to compress command packet. Dropping packet");
|
||||
::free(compressed_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
/* we don't need to make the command longer than it is */
|
||||
if(compressed_size < command.length()) {
|
||||
own_data_buffer = true;
|
||||
data_buffer = (char*) compressed_buffer;
|
||||
own_data_buffer_ptr = compressed_buffer;
|
||||
data_length = compressed_size;
|
||||
head_pflags |= PacketFlag::Compressed;
|
||||
} else {
|
||||
::free(compressed_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ptype_and_flags{(uint8_t) ((uint8_t) ptype | (uint8_t) PacketFlag::NewProtocol)};
|
||||
if(data_length > MAX_COMMAND_PACKET_PAYLOAD_LENGTH) {
|
||||
auto chunk_count = (size_t) ceil((float) data_length / (float) MAX_COMMAND_PACKET_PAYLOAD_LENGTH);
|
||||
auto chunk_size = (size_t) ceil((float) data_length / (float) chunk_count);
|
||||
|
||||
while(true) {
|
||||
auto bytes = min(chunk_size, data_length);
|
||||
auto packet = protocol::allocate_outgoing_packet(bytes);
|
||||
packet->type_and_flags = ptype_and_flags;
|
||||
memcpy(packet->payload, data_buffer, bytes);
|
||||
|
||||
*packets_tail = packet;
|
||||
packets_tail = &packet->next;
|
||||
|
||||
data_length -= bytes;
|
||||
if(data_length == 0) {
|
||||
packet->type_and_flags |= PacketFlag::Fragmented;
|
||||
break;
|
||||
}
|
||||
data_buffer += bytes;
|
||||
}
|
||||
packets_head->type_and_flags |= PacketFlag::Fragmented;
|
||||
} else {
|
||||
auto packet = protocol::allocate_outgoing_packet(data_length);
|
||||
packet->type_and_flags = ptype_and_flags;
|
||||
|
||||
memcpy(packet->payload, data_buffer, data_length);
|
||||
|
||||
packets_head = packet;
|
||||
packets_tail = &packet->next;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard id_lock{this->packet_id_mutex};
|
||||
{
|
||||
uint32_t full_id;
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
full_id = this->packet_id_manager.generate_full_id(ptype);
|
||||
|
||||
head->set_packet_id(full_id & 0xFFFFU);
|
||||
head->generation = full_id >> 16U;
|
||||
|
||||
/* loss stats (In order required so we're using the this->packet_id_mutex) */
|
||||
this->packet_statistics_->send_command(head->packet_type(), full_id);
|
||||
|
||||
head = head->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
packets_head->type_and_flags |= head_pflags;
|
||||
|
||||
/* general stats */
|
||||
{
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
this->callback_connection_stats(this->callback_data, StatisticsCategory::COMMAND, head->packet_length() + 96); /* 96 for the UDP overhead */
|
||||
head = head->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* ack handler */
|
||||
{
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
auto full_packet_id = (uint32_t) (head->generation << 16U) | head->packet_id();
|
||||
|
||||
/* increase a reference for the ack handler */
|
||||
head->ref();
|
||||
|
||||
/* Even thou the packet is yet unencrypted, it will be encrypted with the next write. The next write will be before the next resend because the next ptr must be null in order to resend a packet */
|
||||
if(&head->next == packets_tail)
|
||||
this->acknowledge_manager_.process_packet(ptype, full_packet_id, head, std::move(ack_listener));
|
||||
else
|
||||
this->acknowledge_manager_.process_packet(ptype, full_packet_id, head, nullptr);
|
||||
|
||||
head = head->next;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard qlock{this->write_queue_mutex};
|
||||
*this->encrypt_queue_tail = packets_head;
|
||||
this->encrypt_queue_tail = packets_tail;
|
||||
}
|
||||
this->callback_request_write(this->callback_data);
|
||||
|
||||
if(own_data_buffer)
|
||||
::free(own_data_buffer_ptr);
|
||||
}
|
||||
|
||||
void PacketEncoder::encrypt_pending_packets() {
|
||||
OutgoingServerPacket* packets_head;
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
packets_head = this->encrypt_queue_head;
|
||||
this->encrypt_queue_head = nullptr;
|
||||
this->encrypt_queue_tail = &this->encrypt_queue_head;
|
||||
}
|
||||
|
||||
if(!packets_head)
|
||||
return;
|
||||
|
||||
auto packet = packets_head;
|
||||
auto packet_tail = packet;
|
||||
while(packet) {
|
||||
this->prepare_outgoing_packet(packet);
|
||||
packet = packet->next;
|
||||
if(packet)
|
||||
packet_tail = packet;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
*this->resend_queue_tail = packets_head;
|
||||
this->resend_queue_tail = &packet_tail->next;
|
||||
}
|
||||
}
|
||||
|
||||
bool PacketEncoder::prepare_outgoing_packet(ts::protocol::OutgoingServerPacket *packet) {
|
||||
if(packet->type_and_flags & PacketFlag::Unencrypted) {
|
||||
this->crypt_handler_->write_default_mac(packet->mac);
|
||||
} else {
|
||||
CryptHandler::key_t crypt_key{};
|
||||
CryptHandler::nonce_t crypt_nonce{};
|
||||
std::string error{};
|
||||
|
||||
if(!this->crypt_handler_->encryption_initialized()) {
|
||||
crypt_key = CryptHandler::kDefaultKey;
|
||||
crypt_nonce = CryptHandler::kDefaultNonce;
|
||||
} else {
|
||||
if(!this->crypt_handler_->generate_key_nonce(false, (uint8_t) packet->packet_type(), packet->packet_id(), packet->generation, crypt_key, crypt_nonce)) {
|
||||
this->callback_crypt_error(this->callback_data, CryptError::KEY_GENERATION_FAILED, "");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto crypt_result = this->crypt_handler_->encrypt((char*) packet->packet_data() + ServerPacketP::kHeaderOffset, ServerPacketP::kHeaderLength,
|
||||
packet->payload, packet->payload_size,
|
||||
packet->mac,
|
||||
crypt_key, crypt_nonce, error);
|
||||
if(!crypt_result) {
|
||||
this->callback_crypt_error(this->callback_data, CryptError::KEY_GENERATION_FAILED, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
PacketEncoder::BufferPopResult PacketEncoder::pop_write_buffer(protocol::OutgoingServerPacket *&result) {
|
||||
bool need_prepare_packet{false}, more_packets;
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
if(this->resend_queue_head) {
|
||||
result = this->resend_queue_head;
|
||||
if(result->next) {
|
||||
assert(this->resend_queue_tail != &result->next);
|
||||
this->resend_queue_head = result->next;
|
||||
} else {
|
||||
assert(this->resend_queue_tail == &result->next);
|
||||
this->resend_queue_head = nullptr;
|
||||
this->resend_queue_tail = &this->resend_queue_head;
|
||||
}
|
||||
} else if(this->encrypt_queue_head) {
|
||||
result = this->encrypt_queue_head;
|
||||
if(result->next) {
|
||||
assert(this->encrypt_queue_tail != &result->next);
|
||||
this->encrypt_queue_head = result->next;
|
||||
} else {
|
||||
assert(this->encrypt_queue_tail == &result->next);
|
||||
this->encrypt_queue_head = nullptr;
|
||||
this->encrypt_queue_tail = &this->encrypt_queue_head;
|
||||
}
|
||||
need_prepare_packet = true;
|
||||
} else {
|
||||
return BufferPopResult::DRAINED;
|
||||
}
|
||||
result->next = nullptr;
|
||||
more_packets = this->resend_queue_head != nullptr || this->encrypt_queue_head != nullptr;
|
||||
}
|
||||
|
||||
if(need_prepare_packet)
|
||||
this->prepare_outgoing_packet(result);
|
||||
return more_packets ? BufferPopResult::MORE_AVAILABLE : BufferPopResult::DRAINED;
|
||||
}
|
||||
|
||||
void PacketEncoder::execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next) {
|
||||
std::deque<std::shared_ptr<connection::AcknowledgeManager::Entry>> buffers{};
|
||||
std::string error{};
|
||||
|
||||
this->acknowledge_manager_.execute_resend(now, next, buffers);
|
||||
|
||||
if(!buffers.empty()) {
|
||||
size_t send_count{0};
|
||||
{
|
||||
lock_guard wlock{this->write_queue_mutex};
|
||||
for(auto& buffer : buffers) {
|
||||
auto packet = (protocol::OutgoingServerPacket*) buffer->packet_ptr;
|
||||
|
||||
/* Test if the packet is still in the write/enqueue queue */
|
||||
if(packet->next)
|
||||
continue;
|
||||
|
||||
if(&packet->next == this->encrypt_queue_tail || &packet->next == this->resend_queue_tail)
|
||||
continue;
|
||||
|
||||
packet->ref(); /* for the write queue again */
|
||||
*this->resend_queue_tail = packet;
|
||||
this->resend_queue_tail = &packet->next;
|
||||
|
||||
send_count++;
|
||||
buffer->resend_count++;
|
||||
|
||||
this->packet_statistics_->send_command((protocol::PacketType) buffer->packet_type, buffer->packet_full_id);
|
||||
}
|
||||
}
|
||||
|
||||
this->callback_request_write(this->callback_data);
|
||||
this->callback_resend_stats(this->callback_data, buffers.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool PacketEncoder::wait_empty_write_and_prepare_queue(chrono::time_point<chrono::system_clock> until) {
|
||||
while(true) {
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
if(this->encrypt_queue_head)
|
||||
goto _wait;
|
||||
|
||||
if(this->resend_queue_head)
|
||||
goto _wait;
|
||||
}
|
||||
break;
|
||||
|
||||
_wait:
|
||||
if(until.time_since_epoch().count() != 0 && std::chrono::system_clock::now() > until)
|
||||
return false;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <protocol/Packet.h>
|
||||
#include <protocol/AcknowledgeManager.h>
|
||||
#include <src/ConnectionStatistics.h>
|
||||
#include "./PacketStatistics.h"
|
||||
|
||||
namespace ts::connection {
|
||||
class CryptHandler;
|
||||
class AcknowledgeManager;
|
||||
}
|
||||
|
||||
namespace ts::server::server::udp {
|
||||
|
||||
class PacketEncoder {
|
||||
using AcknowledgeEntry = connection::AcknowledgeManager::Entry;
|
||||
using StatisticsCategory = stats::ConnectionStatistics::category;
|
||||
public:
|
||||
enum struct BufferPopResult {
|
||||
DRAINED,
|
||||
MORE_AVAILABLE
|
||||
};
|
||||
|
||||
enum struct CryptError {
|
||||
KEY_GENERATION_FAILED,
|
||||
ENCRYPT_FAILED /* contains some data */
|
||||
};
|
||||
|
||||
typedef void(*callback_request_write_t)(void* /* user data */);
|
||||
typedef void(*callback_crypt_error_t)(void* /* user data */, const CryptError& /* error */, const std::string& /* details */);
|
||||
|
||||
typedef void(*callback_resend_stats_t)(void* /* user data */, size_t /* resend packets */);
|
||||
typedef void(*callback_resend_failed_t)(void* /* user data */, const std::shared_ptr<AcknowledgeEntry>& /* entry */);
|
||||
|
||||
typedef void(*callback_connection_stats_t)(void* /* user data */, StatisticsCategory::value, size_t /* bytes */);
|
||||
|
||||
explicit PacketEncoder(connection::CryptHandler* /* crypt handler */, client::PacketStatistics* /* packet stats */);
|
||||
~PacketEncoder();
|
||||
|
||||
void reset();
|
||||
|
||||
void send_packet(protocol::OutgoingServerPacket* /* packet */); /* will claim ownership */
|
||||
void send_packet(protocol::PacketType /* type */, protocol::PacketFlag::PacketFlags /* flags */, const void* /* payload */, size_t /* payload length */);
|
||||
void send_command(const std::string_view& /* build command command */, bool /* command low */, std::unique_ptr<threads::Future<bool>> /* acknowledge listener */);
|
||||
|
||||
void send_packet_acknowledge(uint16_t /* packet id */, bool /* acknowledge low */);
|
||||
|
||||
void execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next);
|
||||
void encrypt_pending_packets();
|
||||
|
||||
bool wait_empty_write_and_prepare_queue(std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
|
||||
|
||||
/* if the result is true, ownership has been transferred */
|
||||
BufferPopResult pop_write_buffer(protocol::OutgoingServerPacket*& /* packet */);
|
||||
|
||||
[[nodiscard]] inline auto& acknowledge_manager() { return this->acknowledge_manager_; }
|
||||
|
||||
/* callbacks must be valid all the time! */
|
||||
void* callback_data{nullptr};
|
||||
|
||||
callback_request_write_t callback_request_write{[](auto){}};
|
||||
callback_crypt_error_t callback_crypt_error{[](auto, auto, auto){}};
|
||||
|
||||
callback_resend_stats_t callback_resend_stats{[](auto, auto){}};
|
||||
callback_resend_failed_t callback_resend_failed{[](auto, auto){}};
|
||||
|
||||
callback_connection_stats_t callback_connection_stats{[](auto, auto, auto){}};
|
||||
private:
|
||||
connection::CryptHandler* crypt_handler_{nullptr};
|
||||
client::PacketStatistics* packet_statistics_{nullptr};
|
||||
connection::AcknowledgeManager acknowledge_manager_{};
|
||||
|
||||
spin_mutex write_queue_mutex{};
|
||||
protocol::OutgoingServerPacket* resend_queue_head{nullptr};
|
||||
protocol::OutgoingServerPacket** resend_queue_tail{&resend_queue_head};
|
||||
|
||||
protocol::OutgoingServerPacket* encrypt_queue_head{nullptr};
|
||||
protocol::OutgoingServerPacket** encrypt_queue_tail{&encrypt_queue_head};
|
||||
|
||||
protocol::PacketIdManager packet_id_manager;
|
||||
spin_mutex packet_id_mutex{};
|
||||
|
||||
|
||||
/* thread save function */
|
||||
bool prepare_outgoing_packet(protocol::OutgoingServerPacket* /* packet */);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Created by WolverinDEV on 11/03/2020.
|
||||
//
|
||||
|
||||
#include "PingHandler.h"
|
||||
|
||||
using namespace ts::server::server::udp;
|
||||
|
||||
void PingHandler::reset() {
|
||||
this->last_ping_id = 0;
|
||||
this->current_ping_ = std::chrono::milliseconds{0};
|
||||
|
||||
this->last_recovery_command_send = std::chrono::system_clock::time_point{};
|
||||
this->last_command_acknowledge_ = std::chrono::system_clock::time_point{};
|
||||
|
||||
this->last_response_ = std::chrono::system_clock::time_point{};
|
||||
this->last_request_ = std::chrono::system_clock::time_point{};
|
||||
}
|
||||
|
||||
void PingHandler::received_pong(uint16_t ping_id) {
|
||||
if(this->last_ping_id != ping_id) return;
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
this->current_ping_ = std::chrono::floor<std::chrono::milliseconds>(now - this->last_request_);
|
||||
|
||||
this->last_response_ = now;
|
||||
this->last_command_acknowledge_ = now; /* That's here for purpose!*/
|
||||
}
|
||||
|
||||
void PingHandler::received_command_acknowledged() {
|
||||
this->last_command_acknowledge_ = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
void PingHandler::tick(const std::chrono::system_clock::time_point& now) {
|
||||
if(this->last_request_ + PingHandler::kPingRequestInterval < now)
|
||||
this->send_ping_request(); /* may update last_response_ */
|
||||
|
||||
if(this->last_response_ + PingHandler::kPingTimeout < now) {
|
||||
if(this->last_recovery_command_send + PingHandler::kRecoveryRequestInterval < now)
|
||||
this->send_recovery_request();
|
||||
|
||||
if(this->last_command_acknowledge_ + PingHandler::kRecoveryTimeout < now) {
|
||||
if(auto callback{this->callback_time_outed}; callback)
|
||||
callback(this->callback_argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PingHandler::send_ping_request() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(this->last_response_.time_since_epoch().count() == 0)
|
||||
this->last_response_ = now;
|
||||
|
||||
this->last_request_ = now;
|
||||
|
||||
if(auto callback{this->callback_send_ping}; callback)
|
||||
callback(this->callback_argument, this->last_ping_id);
|
||||
}
|
||||
|
||||
void PingHandler::send_recovery_request() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(this->last_command_acknowledge_.time_since_epoch().count() == 0)
|
||||
this->last_command_acknowledge_ = now;
|
||||
|
||||
this->last_recovery_command_send = now;
|
||||
|
||||
if(auto callback{this->callback_send_recovery_command}; callback)
|
||||
callback(this->callback_argument);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
|
||||
namespace ts::server::server::udp {
|
||||
class PingHandler {
|
||||
public:
|
||||
typedef void(*callback_time_outed_t)(void* /* cb data */);
|
||||
typedef void(*callback_send_ping_t)(void* /* cb data */, uint16_t& /* ping id */);
|
||||
typedef void(*callback_send_recovery_command_t)(void* /* cb data */);
|
||||
|
||||
void reset();
|
||||
|
||||
void tick(const std::chrono::system_clock::time_point&);
|
||||
void received_pong(uint16_t /* ping id */);
|
||||
void received_command_acknowledged();
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds current_ping() const { return this->current_ping_; }
|
||||
[[nodiscard]] inline std::chrono::system_clock::time_point last_ping_response() const { return this->last_response_; }
|
||||
[[nodiscard]] inline std::chrono::system_clock::time_point last_command_acknowledged() const { return this->last_command_acknowledge_; }
|
||||
|
||||
void* callback_argument{nullptr};
|
||||
callback_send_ping_t callback_send_ping{nullptr};
|
||||
callback_send_recovery_command_t callback_send_recovery_command{nullptr};
|
||||
callback_time_outed_t callback_time_outed{nullptr};
|
||||
private:
|
||||
constexpr static std::chrono::milliseconds kPingRequestInterval{1000};
|
||||
constexpr static std::chrono::milliseconds kPingTimeout{15 * 1000};
|
||||
|
||||
constexpr static std::chrono::milliseconds kRecoveryRequestInterval{1000};
|
||||
constexpr static std::chrono::milliseconds kRecoveryTimeout{15 * 1000};
|
||||
|
||||
std::chrono::milliseconds current_ping_{0};
|
||||
|
||||
uint16_t last_ping_id{0};
|
||||
std::chrono::system_clock::time_point last_response_{};
|
||||
std::chrono::system_clock::time_point last_request_{};
|
||||
|
||||
std::chrono::system_clock::time_point last_command_acknowledge_{};
|
||||
std::chrono::system_clock::time_point last_recovery_command_send{};
|
||||
|
||||
void send_ping_request();
|
||||
void send_recovery_request();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// Created by WolverinDEV on 29/07/2020.
|
||||
//
|
||||
|
||||
#include "./ServerCommandExecutor.h"
|
||||
#include "./PacketDecoder.h"
|
||||
#include "./VoiceClientConnection.h"
|
||||
|
||||
using namespace ts;
|
||||
using namespace ts::server::server::udp;
|
||||
|
||||
ServerCommandExecutor::ServerCommandExecutor(VoiceClient *client) : client{client} {}
|
||||
ServerCommandExecutor::~ServerCommandExecutor() {
|
||||
this->reset();
|
||||
}
|
||||
|
||||
void ServerCommandExecutor::reset() {
|
||||
std::unique_lock pc_lock{this->pending_commands_lock};
|
||||
auto head = std::exchange(this->pending_commands_head, nullptr);
|
||||
this->pending_commands_tail = &this->pending_commands_head;
|
||||
pc_lock.unlock();
|
||||
|
||||
while(head) {
|
||||
auto cmd = head->next_command;
|
||||
ReassembledCommand::free(head);
|
||||
head = cmd;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerCommandExecutor::force_insert_command(const pipes::buffer_view &buffer) {
|
||||
auto command = ReassembledCommand::allocate(buffer.length());
|
||||
memcpy(command->command(), buffer.data_ptr(), command->length());
|
||||
this->enqueue_command_execution(command);
|
||||
}
|
||||
|
||||
void ServerCommandExecutor::enqueue_command_execution(ReassembledCommand *command) {
|
||||
assert(!command->next_command);
|
||||
|
||||
bool command_handling_scheduled{false};
|
||||
{
|
||||
std::lock_guard pc_lock{this->pending_commands_lock};
|
||||
*this->pending_commands_tail = command;
|
||||
this->pending_commands_tail = &command->next_command;
|
||||
|
||||
command_handling_scheduled = std::exchange(this->has_command_handling_scheduled, true);
|
||||
}
|
||||
|
||||
if(!command_handling_scheduled) {
|
||||
auto voice_server = this->client->getVoiceServer();
|
||||
if(voice_server)
|
||||
voice_server->schedule_command_handling(&*client);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerCommandExecutor::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) {
|
||||
if(!this->client->getServer() || this->client->connectionState() >= ConnectionState::DISCONNECTING)
|
||||
return;
|
||||
|
||||
std::unique_ptr<ReassembledCommand, void(*)(ReassembledCommand*)> pending_command{nullptr, ReassembledCommand::free};
|
||||
while(true) {
|
||||
{
|
||||
std::lock_guard pc_lock{this->pending_commands_lock};
|
||||
pending_command.reset(this->pending_commands_head);
|
||||
if(!pending_command) {
|
||||
this->has_command_handling_scheduled = false;
|
||||
return;
|
||||
} else if(pending_command->next_command) {
|
||||
this->pending_commands_head = pending_command->next_command;
|
||||
} else {
|
||||
this->pending_commands_head = nullptr;
|
||||
this->pending_commands_tail = &this->pending_commands_head;
|
||||
}
|
||||
}
|
||||
|
||||
auto startTime = std::chrono::system_clock::now();
|
||||
try {
|
||||
this->client->handlePacketCommand(pipes::buffer_view{pending_command->command(), pending_command->length()});
|
||||
} catch (std::exception& ex) {
|
||||
logCritical(this->client->getServerId(), "{} Exception reached root tree! {}", CLIENT_STR_LOG_PREFIX_(this->client), ex.what());
|
||||
}
|
||||
|
||||
auto end = std::chrono::system_clock::now();
|
||||
if(end - startTime > std::chrono::milliseconds(10)) {
|
||||
logError(this->client->getServerId(),
|
||||
"{} Handling of command packet needs more than 10ms ({}ms)",
|
||||
CLIENT_STR_LOG_PREFIX_(this->client),
|
||||
duration_cast<std::chrono::milliseconds>(end - startTime).count()
|
||||
);
|
||||
}
|
||||
|
||||
break; /* Maybe handle more than one command? Maybe some kind of time limit? */
|
||||
}
|
||||
|
||||
auto voice_server = this->client->getVoiceServer();
|
||||
if(voice_server)
|
||||
voice_server->schedule_command_handling(client);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <pipes/buffer.h>
|
||||
|
||||
namespace ts::server {
|
||||
class VoiceClient;
|
||||
}
|
||||
|
||||
namespace ts::server::server::udp {
|
||||
struct ReassembledCommand;
|
||||
|
||||
class ServerCommandExecutor {
|
||||
public:
|
||||
explicit ServerCommandExecutor(VoiceClient*);
|
||||
~ServerCommandExecutor();
|
||||
|
||||
void reset();
|
||||
|
||||
void force_insert_command(const pipes::buffer_view& /* payload */);
|
||||
void enqueue_command_execution(ReassembledCommand*); /* Attention: The method will take ownership of the command */
|
||||
void execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */);
|
||||
private:
|
||||
VoiceClient* client;
|
||||
|
||||
spin_mutex pending_commands_lock{};
|
||||
ReassembledCommand* pending_commands_head{nullptr};
|
||||
ReassembledCommand** pending_commands_tail{&pending_commands_head};
|
||||
bool has_command_handling_scheduled{false}; /* locked by pending_commands_lock */
|
||||
};
|
||||
|
||||
struct ReassembledCommand;
|
||||
}
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "VoiceClient.h"
|
||||
#include "src/VirtualServer.h"
|
||||
#include "../../server/VoiceServer.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "src/manager/ActionLogger.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -54,54 +56,22 @@ void VoiceClient::sendCommand0(const std::string_view& cmd, bool low, std::uniqu
|
||||
logTrace(this->getServerId(), "{}[Command][Server -> Client] Sending command {}. Command low: {}. Full command: {}", CLIENT_STR_LOG_PREFIX, cmd.substr(0, cmd.find(' ')), low, cmd);
|
||||
#endif
|
||||
}
|
||||
void VoiceClient::sendAcknowledge(uint16_t packetId, bool low) {
|
||||
char buffer[2];
|
||||
le2be16(packetId, buffer);
|
||||
|
||||
auto pflags = PacketFlag::Unencrypted | PacketFlag::NewProtocol;
|
||||
this->connection->send_packet(low ? protocol::PacketType::ACK_LOW : protocol::PacketType::ACK, (PacketFlag::PacketFlag) pflags, buffer, 2);
|
||||
#ifdef PKT_LOG_ACK
|
||||
logTrace(this->getServerId(), "{}[Acknowledge][Server -> Client] Sending acknowledge for {}", CLIENT_STR_LOG_PREFIX, packetId);
|
||||
#endif
|
||||
}
|
||||
|
||||
void VoiceClient::tick(const std::chrono::system_clock::time_point &time) {
|
||||
SpeakingClient::tick(time);
|
||||
{
|
||||
ALARM_TIMER(A1, "VoiceClient::tick", milliseconds(3));
|
||||
if(this->state == ConnectionState::CONNECTED) {
|
||||
if(this->lastPingRequest > this->lastPingResponse) { //Client is behind :)
|
||||
if(this->lastPingRequest - this->lastPingResponse > chrono::seconds(20)) {
|
||||
debugMessage(this->getServerId(), "{} Got a ping timeout. (Last successful ping: {}ms ago. Last request {}ms. Last response {}ms). Trying to recover via command acknowledge.",
|
||||
CLIENT_STR_LOG_PREFIX,
|
||||
duration_cast<milliseconds>(this->lastPingRequest - this->lastPingResponse).count(),
|
||||
duration_cast<milliseconds>(time - this->lastPingRequest).count(),
|
||||
duration_cast<milliseconds>(time - this->lastPingResponse).count());
|
||||
|
||||
bool force;
|
||||
this->request_connection_info(nullptr, force);
|
||||
this->lastPingResponse = system_clock::now();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(time - this->lastPingRequest >= chrono::milliseconds(1000)) {
|
||||
//TODO calculate the ping smooth
|
||||
if(this->lastPingResponse < this->lastPingRequest){
|
||||
if(time - this->lastPingRequest >= chrono::milliseconds(1500)) { //Max
|
||||
this->sendPingRequest();
|
||||
}
|
||||
} else
|
||||
this->sendPingRequest();
|
||||
}
|
||||
|
||||
this->connection->ping_handler().tick(time);
|
||||
this->connection->packet_statistics().tick();
|
||||
} else if(this->state == ConnectionState::INIT_LOW || this->state == ConnectionState::INIT_HIGH) {
|
||||
if(this->last_packet_handshake.time_since_epoch().count() != 0) {
|
||||
if(time - this->last_packet_handshake > seconds(5)) {
|
||||
auto last_command = this->connection->crypt_setup_handler().last_handled_command();
|
||||
if(last_command.time_since_epoch().count() != 0) {
|
||||
if(time - last_command > seconds(5)) {
|
||||
debugMessage(this->getServerId(), "{} Got handshake timeout. {}. State: {} Time: {}", CLIENT_STR_LOG_PREFIX,
|
||||
this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()),
|
||||
this->state == ConnectionState::INIT_HIGH ? "INIT_HIGH" : "INIT_LOW",
|
||||
duration_cast<seconds>(time - this->last_packet_handshake).count()
|
||||
duration_cast<seconds>(time - last_command).count()
|
||||
);
|
||||
this->close_connection(system_clock::now() + seconds(1));
|
||||
}
|
||||
@@ -110,6 +80,10 @@ void VoiceClient::tick(const std::chrono::system_clock::time_point &time) {
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::milliseconds VoiceClient::current_ping() {
|
||||
return this->connection->ping_handler().current_ping();
|
||||
}
|
||||
|
||||
bool VoiceClient::disconnect(const std::string &reason) {
|
||||
return this->disconnect(VREASON_SERVER_KICK, reason, this->server->serverRoot, true);
|
||||
}
|
||||
@@ -146,6 +120,11 @@ bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reas
|
||||
cmd["invokeruid"] = invoker->getUid();
|
||||
}
|
||||
|
||||
auto old_channel = this->currentChannel;
|
||||
if(old_channel) {
|
||||
serverInstance->action_logger()->client_channel_logger.log_client_leave(this->getServerId(), this->ref(), old_channel->channelId(), old_channel->name());
|
||||
}
|
||||
|
||||
if(notify_viewer && this->server) {
|
||||
unique_lock channel_lock(this->server->channel_tree_lock);
|
||||
this->server->client_move(this->ref(), nullptr, invoker, reason, reason_id, false, channel_lock);
|
||||
@@ -215,7 +194,7 @@ bool VoiceClient::close_connection(const system_clock::time_point &timeout) {
|
||||
while(this->state == DISCONNECTING_FLUSHING) {
|
||||
if(system_clock::now() > timeout){
|
||||
auto write_queue_flushed = this->connection->wait_empty_write_and_prepare_queue(timeout);
|
||||
auto acknowledge_received = connection->acknowledge_handler.awaiting_acknowledge() == 0;
|
||||
auto acknowledge_received = connection->packet_encoder().acknowledge_manager().awaiting_acknowledge() == 0;
|
||||
|
||||
if(write_queue_flushed && acknowledge_received)
|
||||
break;
|
||||
@@ -226,7 +205,7 @@ bool VoiceClient::close_connection(const system_clock::time_point &timeout) {
|
||||
if(!this->connection->wait_empty_write_and_prepare_queue(timeout))
|
||||
continue;
|
||||
|
||||
if(connection->acknowledge_handler.awaiting_acknowledge() > 0) {
|
||||
if(connection->packet_encoder().acknowledge_manager().awaiting_acknowledge() > 0) {
|
||||
usleep(5000);
|
||||
continue;
|
||||
}
|
||||
@@ -265,7 +244,7 @@ void VoiceClient::finalDisconnect() {
|
||||
}
|
||||
|
||||
void VoiceClient::execute_handle_packet(const std::chrono::system_clock::time_point &time) {
|
||||
this->connection->execute_handle_command_packets(time);
|
||||
this->server_command_executor_.execute_handle_command_packets(time);
|
||||
}
|
||||
|
||||
void VoiceClient::send_voice_packet(const pipes::buffer_view &voice_buffer, const SpeakingClient::VoicePacketFlags &flags) {
|
||||
@@ -278,18 +257,22 @@ void VoiceClient::send_voice_packet(const pipes::buffer_view &voice_buffer, cons
|
||||
this->connection->send_packet(PacketType::VOICE, packet_flags, voice_buffer.data_ptr<void>(), voice_buffer.length());
|
||||
}
|
||||
|
||||
void VoiceClient::send_voice_whisper_packet(const pipes::buffer_view &voice_buffer, const SpeakingClient::VoicePacketFlags &flags) {
|
||||
void VoiceClient::send_voice_whisper_packet(const pipes::buffer_view &teamspeak_packet, const pipes::buffer_view &teaspeak_packet, const SpeakingClient::VoicePacketFlags &flags) {
|
||||
PacketFlag::PacketFlags packet_flags{PacketFlag::None};
|
||||
packet_flags |= flags.encrypted ? 0U : PacketFlag::Unencrypted;
|
||||
packet_flags |= flags.head ? PacketFlag::Compressed : 0U;
|
||||
packet_flags |= flags.fragmented ? PacketFlag::Fragmented : 0U;
|
||||
packet_flags |= flags.new_protocol ? PacketFlag::NewProtocol : 0U;
|
||||
|
||||
this->connection->send_packet(PacketType::VOICE_WHISPER, packet_flags, voice_buffer.data_ptr<void>(), voice_buffer.length());
|
||||
if(this->getType() == ClientType::CLIENT_TEASPEAK) {
|
||||
this->connection->send_packet(PacketType::VOICE_WHISPER, packet_flags, teaspeak_packet.data_ptr<void>(), teaspeak_packet.length());
|
||||
} else {
|
||||
this->connection->send_packet(PacketType::VOICE_WHISPER, packet_flags, teamspeak_packet.data_ptr<void>(), teamspeak_packet.length());
|
||||
}
|
||||
}
|
||||
|
||||
float VoiceClient::current_ping_deviation() {
|
||||
return this->connection->getAcknowledgeManager().current_rttvar();
|
||||
return this->connection->packet_encoder().acknowledge_manager().current_rttvar();
|
||||
}
|
||||
|
||||
float VoiceClient::current_packet_loss() const {
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
#include "../ConnectedClient.h"
|
||||
#include "protocol/CryptHandler.h"
|
||||
#include "VoiceClientConnection.h"
|
||||
#include "PrecomputedPuzzles.h"
|
||||
#include "src/server/PrecomputedPuzzles.h"
|
||||
#include "../../lincense/TeamSpeakLicense.h"
|
||||
#include "./ServerCommandExecutor.h"
|
||||
|
||||
//#define LOG_INCOMPING_PACKET_FRAGMENTS
|
||||
//#define LOG_AUTO_ACK_AUTORESPONSE
|
||||
//#define LOG_AUTO_ACK_REQUEST
|
||||
//#define LOG_AUTO_ACK_RESPONSE
|
||||
//#define LOG_PKT_RESEND
|
||||
|
||||
#define PKT_LOG_CMD
|
||||
//#define PKT_LOG_VOICE
|
||||
@@ -37,6 +37,11 @@ namespace ts {
|
||||
class VoiceClientConnection;
|
||||
}
|
||||
namespace server {
|
||||
namespace server::udp {
|
||||
class ServerCommandExecutor;
|
||||
class CryptSetupHandler;
|
||||
}
|
||||
|
||||
class VirtualServer;
|
||||
|
||||
class VoiceClient : public SpeakingClient {
|
||||
@@ -46,28 +51,32 @@ namespace ts {
|
||||
friend class ts::connection::VoiceClientConnection;
|
||||
friend class ConnectedClient;
|
||||
friend class io::IOServerHandler;
|
||||
friend class server::udp::ServerCommandExecutor;
|
||||
friend class server::udp::CryptSetupHandler;
|
||||
using ServerCommandExecutor = ts::server::server::udp::ServerCommandExecutor;
|
||||
public:
|
||||
VoiceClient(const std::shared_ptr<VoiceServer>& server,const sockaddr_storage*);
|
||||
~VoiceClient();
|
||||
VoiceClient(const std::shared_ptr<VoiceServer>& server, const sockaddr_storage*);
|
||||
~VoiceClient() override;
|
||||
|
||||
bool close_connection(const std::chrono::system_clock::time_point &timeout) override;
|
||||
bool disconnect(const std::string&) override;
|
||||
bool disconnect(ViewReasonId /* reason type */, const std::string& /* reason */, const std::shared_ptr<ts::server::ConnectedClient>& /* invoker */, bool /* notify viewer */);
|
||||
|
||||
virtual void sendCommand(const ts::Command &command, bool low = false) { return this->sendCommand0(command.build(), low); }
|
||||
virtual void sendCommand(const ts::command_builder &command, bool low) { return this->sendCommand0(command.build(), low); }
|
||||
void sendCommand(const ts::Command &command, bool low = false) override { return this->sendCommand0(command.build(), low); }
|
||||
void sendCommand(const ts::command_builder &command, bool low) override { return this->sendCommand0(command.build(), low); }
|
||||
|
||||
/* Note: Order is only guaranteed if progressDirectly is on! */
|
||||
virtual void sendCommand0(const std::string_view& /* data */, bool low = false, std::unique_ptr<threads::Future<bool>> listener = nullptr);
|
||||
virtual void sendAcknowledge(uint16_t packetId, bool low = false);
|
||||
|
||||
connection::VoiceClientConnection* getConnection(){ return connection; }
|
||||
std::shared_ptr<VoiceServer> getVoiceServer(){ return voice_server; }
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds current_ping(){ return ping; }
|
||||
[[nodiscard]] std::chrono::milliseconds current_ping();
|
||||
[[nodiscard]] float current_ping_deviation();
|
||||
|
||||
[[nodiscard]] float current_packet_loss() const;
|
||||
|
||||
[[nodiscard]] inline auto& server_command_executor() { return this->server_command_executor_; }
|
||||
private:
|
||||
connection::VoiceClientConnection* connection;
|
||||
|
||||
@@ -77,62 +86,30 @@ namespace ts {
|
||||
void initialize();
|
||||
virtual void tick(const std::chrono::system_clock::time_point &time) override;
|
||||
|
||||
/* Attention these handle callbacks are not thread save! */
|
||||
void handlePacketCommand(const pipes::buffer_view&);
|
||||
void handlePacketAck(const protocol::ClientPacketParser&);
|
||||
void handlePacketVoice(const protocol::ClientPacketParser&);
|
||||
void handlePacketVoiceWhisper(const protocol::ClientPacketParser&);
|
||||
void handlePacketPing(const protocol::ClientPacketParser&);
|
||||
void handlePacketInit(const protocol::ClientPacketParser&);
|
||||
|
||||
//Handshake helpers
|
||||
|
||||
|
||||
public:
|
||||
void send_voice_packet(const pipes::buffer_view &packet, const VoicePacketFlags &flags) override;
|
||||
void send_voice_whisper_packet(const pipes::buffer_view &packet, const VoicePacketFlags &flags) override;
|
||||
void send_voice_whisper_packet(
|
||||
const pipes::buffer_view &/* teamspeak packet */,
|
||||
const pipes::buffer_view &/* teaspeak packet */,
|
||||
const VoicePacketFlags &flags
|
||||
) override;
|
||||
|
||||
protected:
|
||||
virtual command_result handleCommand(Command &command) override;
|
||||
|
||||
//Some helper method
|
||||
void sendPingRequest();
|
||||
|
||||
//Ping/pong
|
||||
uint16_t lastPingId = 0;
|
||||
std::chrono::milliseconds ping = std::chrono::milliseconds(0);
|
||||
std::chrono::system_clock::time_point lastPingResponse;
|
||||
std::chrono::system_clock::time_point lastPingRequest;
|
||||
|
||||
std::chrono::system_clock::time_point last_packet_handshake;
|
||||
|
||||
private:
|
||||
int socket = 0;
|
||||
io::pktinfo_storage address_info;
|
||||
|
||||
void finalDisconnect();
|
||||
bool final_disconnected = false;
|
||||
|
||||
//General TS3 manager commands
|
||||
command_result handleCommandClientInitIv(Command&);
|
||||
command_result handleCommandClientEk(Command&);
|
||||
command_result handleCommandClientInit(Command&) override;
|
||||
command_result handleCommandClientDisconnect(Command&);
|
||||
|
||||
//Locked by finalDisconnect, disconnect and close connection
|
||||
std::shared_ptr<threads::Thread> flushing_thread;
|
||||
|
||||
struct {
|
||||
bool client_init = false;
|
||||
bool new_protocol = false;
|
||||
bool protocol_encrypted = false;
|
||||
bool is_teaspeak_client = false;
|
||||
|
||||
uint32_t client_time = 0;
|
||||
std::string alpha;
|
||||
std::string beta;
|
||||
std::shared_ptr<LicenseChainData> chain_data;
|
||||
std::shared_ptr<ecc_key> remote_key;
|
||||
} crypto;
|
||||
ServerCommandExecutor server_command_executor_{this};
|
||||
|
||||
std::shared_ptr<event::ProxiedEventEntry<VoiceClient>> event_handle_packet;
|
||||
void execute_handle_packet(const std::chrono::system_clock::time_point& /* scheduled */);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <src/client/SpeakingClient.h>
|
||||
|
||||
#include "../../InstanceHandler.h"
|
||||
#include "../../geo/GeoLocation.h"
|
||||
#include "VoiceClient.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -15,6 +14,25 @@ using namespace ts::server;
|
||||
using namespace ts::protocol;
|
||||
using namespace ts;
|
||||
|
||||
void VoiceClient::handlePacketCommand(const pipes::buffer_view& command_string) {
|
||||
std::unique_ptr<Command> command;
|
||||
command_result result{};
|
||||
try {
|
||||
command = make_unique<Command>(Command::parse(command_string, true, !ts::config::server::strict_ut8_mode));
|
||||
} catch(std::invalid_argument& ex) {
|
||||
result.reset(command_result{error::parameter_convert, std::string{ex.what()}});
|
||||
goto handle_error;
|
||||
} catch(std::exception& ex) {
|
||||
result.reset(command_result{error::parameter_convert, std::string{ex.what()}});
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
this->handleCommandFull(*command, true);
|
||||
return;
|
||||
handle_error:
|
||||
this->notifyError(result);
|
||||
result.release_data();
|
||||
}
|
||||
|
||||
command_result VoiceClient::handleCommand(ts::Command &command) {
|
||||
threads::MutexLock l2(this->command_lock);
|
||||
@@ -56,23 +74,25 @@ inline bool calculate_security_level(int& result, ecc_key* pubKey, size_t offset
|
||||
}
|
||||
|
||||
command_result VoiceClient::handleCommandClientInit(Command &cmd) {
|
||||
this->crypto.client_init = true;
|
||||
this->connection->acknowledge_handler.reset();
|
||||
|
||||
if(this->getType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||
int securityLevel;
|
||||
if(!calculate_security_level(securityLevel, this->crypto.remote_key.get(), cmd["client_key_offset"])) {
|
||||
logError(this->getServerId(), "[{}] Failed to calculate security level. Error code: {}", CLIENT_STR_LOG_PREFIX, securityLevel);
|
||||
auto client_identity = this->connection->crypt_setup_handler().identity_key();
|
||||
|
||||
int security_level;
|
||||
if(!calculate_security_level(security_level, &*client_identity, cmd["client_key_offset"])) {
|
||||
logError(this->getServerId(), "[{}] Failed to calculate security level. Error code: {}", CLIENT_STR_LOG_PREFIX, security_level);
|
||||
return command_result{error::vs_critical};
|
||||
}
|
||||
if(securityLevel < 8)
|
||||
|
||||
if(security_level < 8) {
|
||||
return command_result{error::client_could_not_validate_identity};
|
||||
}
|
||||
|
||||
auto requiredLevel = this->getServer()->properties()[property::VIRTUALSERVER_NEEDED_IDENTITY_SECURITY_LEVEL].as<uint8_t>();
|
||||
if(securityLevel < requiredLevel) return command_result{error::client_could_not_validate_identity, to_string(requiredLevel)};
|
||||
if(security_level < requiredLevel) {
|
||||
return command_result{error::client_could_not_validate_identity, to_string(requiredLevel)};
|
||||
}
|
||||
}
|
||||
|
||||
this->lastPingResponse = std::chrono::system_clock::now();
|
||||
return SpeakingClient::handleCommandClientInit(cmd);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#include <misc/endianness.h>
|
||||
#include <algorithm>
|
||||
#include <log/LogUtils.h>
|
||||
#include "../../server/VoiceServer.h"
|
||||
#include <misc/memtracker.h>
|
||||
#include <protocol/Packet.h>
|
||||
#include <ThreadPool/Timer.h>
|
||||
#include "VoiceClientConnection.h"
|
||||
#include "src/client/ConnectedClient.h"
|
||||
#include "VoiceClient.h"
|
||||
|
||||
#include "../../server/VoiceServer.h"
|
||||
#include "./VoiceClientConnection.h"
|
||||
#include "./VoiceClient.h"
|
||||
|
||||
|
||||
//#define LOG_AUTO_ACK_AUTORESPONSE
|
||||
@@ -29,746 +28,278 @@ using namespace ts::connection;
|
||||
using namespace ts::protocol;
|
||||
using namespace ts::server;
|
||||
|
||||
VoiceClientConnection::VoiceClientConnection(VoiceClient* client) : client(client) {
|
||||
VoiceClientConnection::VoiceClientConnection(VoiceClient* client)
|
||||
: current_client{client},
|
||||
crypt_handler{},
|
||||
packet_decoder_{&this->crypt_handler},
|
||||
packet_encoder_{&this->crypt_handler, &this->packet_statistics_},
|
||||
crypt_setup_handler_{this} {
|
||||
memtrack::allocated<VoiceClientConnection>(this);
|
||||
|
||||
this->acknowledge_handler.destroy_packet = [](void* packet) {
|
||||
reinterpret_cast<OutgoingServerPacket*>(packet)->unref();
|
||||
};
|
||||
this->packet_decoder_.callback_argument = this;
|
||||
this->packet_decoder_.callback_decoded_packet = VoiceClientConnection::callback_packet_decoded;
|
||||
this->packet_decoder_.callback_decoded_command = VoiceClientConnection::callback_command_decoded;
|
||||
this->packet_decoder_.callback_send_acknowledge = VoiceClientConnection::callback_send_acknowledge;
|
||||
|
||||
this->crypt_handler.reset();
|
||||
this->packet_encoder_.callback_data = this;
|
||||
this->packet_encoder_.callback_request_write = VoiceClientConnection::callback_request_write;
|
||||
this->packet_encoder_.callback_crypt_error = VoiceClientConnection::callback_encode_crypt_error;
|
||||
this->packet_encoder_.callback_resend_failed = VoiceClientConnection::callback_resend_failed;
|
||||
this->packet_encoder_.callback_resend_stats = VoiceClientConnection::callback_resend_statistics;
|
||||
this->packet_encoder_.callback_connection_stats = VoiceClientConnection::callback_outgoing_connection_statistics;
|
||||
|
||||
this->ping_handler_.callback_argument = this;
|
||||
this->ping_handler_.callback_send_ping = VoiceClientConnection::callback_ping_send;
|
||||
this->ping_handler_.callback_send_recovery_command = VoiceClientConnection::callback_ping_send_recovery;
|
||||
this->ping_handler_.callback_time_outed = VoiceClientConnection::callback_ping_timeout;
|
||||
|
||||
this->virtual_server_id_ = client->getServerId();
|
||||
debugMessage(client->getServer()->getServerId(), "Allocated new voice client connection at {}", (void*) this);
|
||||
}
|
||||
|
||||
VoiceClientConnection::~VoiceClientConnection() {
|
||||
this->reset();
|
||||
this->client = nullptr;
|
||||
this->current_client = nullptr;
|
||||
memtrack::freed<VoiceClientConnection>(this);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::triggerWrite() {
|
||||
if(this->client->voice_server)
|
||||
this->client->voice_server->triggerWrite(dynamic_pointer_cast<VoiceClient>(this->client->_this.lock()));
|
||||
std::string VoiceClientConnection::log_prefix() {
|
||||
auto client = this->getCurrentClient();
|
||||
if(!client) return "[unknown / unknown]"; /* FIXME: Get the IP address here! */
|
||||
|
||||
return CLIENT_STR_LOG_PREFIX_(client);
|
||||
}
|
||||
|
||||
#ifdef CLIENT_LOG_PREFIX
|
||||
#undef CLIENT_LOG_PREFIX
|
||||
#endif
|
||||
#define CLIENT_LOG_PREFIX "[" << this->client->getPeerIp() << ":" << this->client->getPeerPort() << " | " << this->client->getDisplayName() << "]"
|
||||
|
||||
//Message handle methods
|
||||
void VoiceClientConnection::triggerWrite() {
|
||||
if(this->current_client->voice_server)
|
||||
this->current_client->voice_server->triggerWrite(dynamic_pointer_cast<VoiceClient>(this->current_client->_this.lock()));
|
||||
}
|
||||
|
||||
void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& buffer) {
|
||||
#ifdef FUZZING_TESTING_INCOMMING
|
||||
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
|
||||
if (this->client->state == ConnectionState::CONNECTED) {
|
||||
#endif
|
||||
if ((rand() % FUZZING_TESTING_DROP_MAX) < FUZZING_TESTING_DROP) {
|
||||
debugMessage(this->client->getServerId(), "{}[FUZZING] Dropping incoming packet of length {}", CLIENT_STR_LOG_PREFIX_(this->client), buffer.length());
|
||||
return;
|
||||
}
|
||||
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
ClientPacketParser packet_parser{buffer};
|
||||
if(!packet_parser.valid()) {
|
||||
logTrace(this->client->getServerId(), "{} Received invalid packet. Dropping.", CLIENT_STR_LOG_PREFIX_(this->client));
|
||||
if(!packet_parser.valid())
|
||||
return;
|
||||
}
|
||||
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
|
||||
packet_parser.set_estimated_generation(this->incoming_generation_estimators[packet_parser.type()].visit_packet(packet_parser.packet_id()));
|
||||
|
||||
|
||||
#ifndef CONNECTION_NO_STATISTICS
|
||||
if(this->client) {
|
||||
auto stats = this->client->connectionStatistics;
|
||||
if(this->current_client) {
|
||||
auto stats = this->current_client->connectionStatistics;
|
||||
stats->logIncomingPacket(stats::ConnectionStatistics::category::from_type(packet_parser.type()), buffer.length() + 96); /* 96 for the UDP packet overhead */
|
||||
}
|
||||
this->packet_statistics().received_packet((protocol::PacketType) packet_parser.type(), packet_parser.full_packet_id());
|
||||
#endif
|
||||
|
||||
auto is_command = packet_parser.type() == protocol::COMMAND || packet_parser.type() == protocol::COMMAND_LOW;
|
||||
std::string error{};
|
||||
auto result = this->packet_decoder_.process_incoming_data(packet_parser, error);
|
||||
using PacketProcessResult = server::server::udp::PacketProcessResult;
|
||||
switch (result) {
|
||||
case PacketProcessResult::SUCCESS:
|
||||
case PacketProcessResult::FUZZ_DROPPED: /* maybe some kind of log? */
|
||||
case PacketProcessResult::DECRYPT_FAILED: /* Silently drop this packet */
|
||||
case PacketProcessResult::DUPLICATED_PACKET: /* no action needed, acknowledge should be send already */
|
||||
break;
|
||||
|
||||
/* in previous versions we checked if the arrived packet is "worth decoding".
|
||||
* But since in general a command buffer underflow is much more unlikely, especially because most packets are not even command packets,
|
||||
* it's better we just skip that step and decode it anyways */
|
||||
case PacketProcessResult::DECRYPT_KEY_GEN_FAILED:
|
||||
/* no action needed, acknowledge should be send */
|
||||
logCritical(this->virtual_server_id_, "{} Failed to generate decrypt key. Dropping packet.", this->log_prefix());
|
||||
break;
|
||||
|
||||
#if 0
|
||||
/* pretest if the packet is worth the effort of decoding it */
|
||||
if(is_command) {
|
||||
/* handle the order stuff */
|
||||
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(packet_parser.type())];
|
||||
|
||||
unique_lock queue_lock(fragment_buffer.buffer_lock);
|
||||
auto result = fragment_buffer.accept_index(packet_parser.packet_id());
|
||||
if(result != 0) { /* packet index is ahead buffer index */
|
||||
debugMessage(this->client->getServerId(), "{} Dropping command packet because command assembly buffer has an {} ({}|{}|{})",
|
||||
CLIENT_STR_LOG_PREFIX_(this->client),
|
||||
result == -1 ? "underflow" : "overflow",
|
||||
fragment_buffer.capacity(),
|
||||
fragment_buffer.current_index(),
|
||||
packet_parser.packet_id()
|
||||
case PacketProcessResult::BUFFER_OVERFLOW:
|
||||
case PacketProcessResult::BUFFER_UNDERFLOW:
|
||||
debugMessage(this->virtual_server_id_, "{} Dropping command packet because command assembly buffer has an {}: {}",
|
||||
this->log_prefix(),
|
||||
result == PacketProcessResult::BUFFER_UNDERFLOW ? "underflow" : "overflow",
|
||||
error
|
||||
);
|
||||
break;
|
||||
|
||||
if(result == -1) { /* underflow */
|
||||
/* we've already got the packet, but the client dosn't know that so we've to send the acknowledge again */
|
||||
if(this->client->crypto.protocol_encrypted)
|
||||
this->client->sendAcknowledge(packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
case PacketProcessResult::UNKNOWN_ERROR:
|
||||
logCritical(this->virtual_server_id_, "{} Having an unknown error while processing a incoming packet: {}",
|
||||
this->log_prefix(),
|
||||
error
|
||||
);
|
||||
goto disconnect_client;
|
||||
|
||||
//NOTICE I found out that the Compressed flag is set if the packet contains an audio header
|
||||
case PacketProcessResult::COMMAND_BUFFER_OVERFLOW:
|
||||
debugMessage(this->virtual_server_id_, "{} Having a command buffer overflow. This might cause the client to drop.", this->log_prefix());
|
||||
break;
|
||||
|
||||
if(this->client->state == ConnectionState::INIT_LOW && packet_parser.type() != protocol::INIT1)
|
||||
return;
|
||||
case PacketProcessResult::COMMAND_DECOMPRESS_FAILED:
|
||||
logWarning(this->virtual_server_id_, "{} Failed to decompress a command packet. Dropping command.", this->log_prefix());
|
||||
break;
|
||||
|
||||
/* decrypt the packet if needed */
|
||||
if(packet_parser.is_encrypted()) {
|
||||
std::string error;
|
||||
case PacketProcessResult::COMMAND_TOO_LARGE:
|
||||
logWarning(this->virtual_server_id_, "{} Received a too large command. Dropping client.", this->log_prefix());
|
||||
goto disconnect_client;
|
||||
|
||||
CryptHandler::key_t crypt_key{};
|
||||
CryptHandler::nonce_t crypt_nonce{};
|
||||
case PacketProcessResult::COMMAND_SEQUENCE_LENGTH_TOO_LONG:
|
||||
logWarning(this->virtual_server_id_, "{} Received a too long command sequence. Dropping client.", this->log_prefix());
|
||||
goto disconnect_client;
|
||||
|
||||
auto data = (uint8_t*) packet_parser.mutable_data_ptr();
|
||||
bool use_default_key{!this->client->crypto.protocol_encrypted}, decrypt_result;
|
||||
|
||||
decrypt_packet:
|
||||
if(use_default_key) {
|
||||
crypt_key = CryptHandler::default_key;
|
||||
crypt_nonce = CryptHandler::default_nonce;
|
||||
} else {
|
||||
if(!this->crypt_handler.generate_key_nonce(true, packet_parser.type(), packet_parser.packet_id(), packet_parser.estimated_generation(), crypt_key, crypt_nonce)) {
|
||||
logError(this->client->getServerId(), "{} Failed to generate crypt key/nonce. This should never happen! Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
decrypt_result = this->crypt_handler.decrypt(
|
||||
data + ClientPacketParser::kHeaderOffset, ClientPacketParser::kHeaderLength,
|
||||
data + ClientPacketParser::kPayloadOffset, packet_parser.payload_length(),
|
||||
data,
|
||||
crypt_key, crypt_nonce,
|
||||
error
|
||||
);
|
||||
|
||||
if(!decrypt_result) {
|
||||
if(!this->client->crypto.client_init) {
|
||||
if(use_default_key) {
|
||||
logTrace(this->client->getServerId(), "{} Failed to decrypt packet with default key ({}). Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client), error);
|
||||
return;
|
||||
} else {
|
||||
logTrace(this->client->getServerId(), "{} Failed to decrypt packet ({}). Trying with default key.", CLIENT_STR_LOG_PREFIX_(this->client), error);
|
||||
use_default_key = true;
|
||||
goto decrypt_packet;
|
||||
}
|
||||
} else {
|
||||
logTrace(this->client->getServerId(), "{} Failed to decrypt packet ({}). Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client), error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
packet_parser.set_decrypted();
|
||||
} else if(is_command && this->client->state != ConnectionState::INIT_HIGH) {
|
||||
logTrace(this->client->getServerId(), "{} Voice client {}/{} tried to send a unencrypted command packet. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client), client->getDisplayName(), this->client->getLoggingPeerIp());
|
||||
return;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef LOG_INCOMPING_PACKET_FRAGMENTS
|
||||
debugMessage(lstream << CLIENT_LOG_PREFIX << "Recived packet. PacketId: " << packet->packetId() << " PacketType: " << packet->type().name() << " Flags: " << packet->flags() << " - " << packet->data() << endl);
|
||||
#endif
|
||||
if(is_command) {
|
||||
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(packet_parser.type())];
|
||||
CommandFragment fragment_entry{
|
||||
packet_parser.packet_id(),
|
||||
packet_parser.estimated_generation(),
|
||||
return;
|
||||
|
||||
packet_parser.flags(),
|
||||
(uint32_t) packet_parser.payload_length(),
|
||||
packet_parser.payload().own_buffer()
|
||||
};
|
||||
disconnect_client:;
|
||||
/* FIXME: Disconnect the client */
|
||||
}
|
||||
|
||||
{
|
||||
unique_lock queue_lock(fragment_buffer.buffer_lock);
|
||||
void VoiceClientConnection::callback_send_acknowledge(void *ptr_this, uint16_t packet_id, bool command_low) {
|
||||
reinterpret_cast<VoiceClientConnection*>(ptr_this)->packet_encoder_.send_packet_acknowledge(packet_id, command_low);
|
||||
}
|
||||
|
||||
if(!fragment_buffer.insert_index(packet_parser.packet_id(), std::move(fragment_entry))) {
|
||||
auto ignore_type = fragment_buffer.accept_index(packet_parser.packet_id());
|
||||
debugMessage(this->client->getServerId(), "{} Dropping command packet because command assembly buffer has an {} ({}|{}|{})",
|
||||
CLIENT_STR_LOG_PREFIX_(this->client),
|
||||
ignore_type == -1 ? "underflow" : "overflow",
|
||||
fragment_buffer.capacity(),
|
||||
fragment_buffer.current_index(),
|
||||
packet_parser.packet_id()
|
||||
);
|
||||
void VoiceClientConnection::callback_packet_decoded(void *ptr_this, const ts::protocol::ClientPacketParser &packet) {
|
||||
auto connection = reinterpret_cast<VoiceClientConnection*>(ptr_this);
|
||||
switch (packet.type()) {
|
||||
case protocol::VOICE:
|
||||
connection->handlePacketVoice(packet);
|
||||
break;
|
||||
|
||||
if(ignore_type == -1) { /* underflow */
|
||||
/* we've already got the packet, but the client dosn't know that so we've to send the acknowledge again */
|
||||
this->client->sendAcknowledge(packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->client->sendAcknowledge(packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
|
||||
case protocol::VOICE_WHISPER:
|
||||
connection->handlePacketVoiceWhisper(packet);
|
||||
break;
|
||||
|
||||
auto voice_server = this->client->voice_server;
|
||||
if(voice_server)
|
||||
voice_server->schedule_command_handling(this->client);
|
||||
} else {
|
||||
if(packet_parser.type() == protocol::VOICE)
|
||||
this->client->handlePacketVoice(packet_parser);
|
||||
else if(packet_parser.type() == protocol::VOICE_WHISPER)
|
||||
this->client->handlePacketVoiceWhisper(packet_parser);
|
||||
else if(packet_parser.type() == protocol::ACK || packet_parser.type() == protocol::ACK_LOW)
|
||||
this->client->handlePacketAck(packet_parser);
|
||||
else if(packet_parser.type() == protocol::PING || packet_parser.type() == protocol::PONG)
|
||||
this->client->handlePacketPing(packet_parser);
|
||||
else {
|
||||
logError(this->client->getServerId(), "{} Received hand decoded packet, but we've no method to handle it. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
|
||||
}
|
||||
case protocol::ACK:
|
||||
connection->handlePacketAck(packet);
|
||||
break;
|
||||
|
||||
case protocol::ACK_LOW:
|
||||
connection->handlePacketAckLow(packet);
|
||||
break;
|
||||
|
||||
case protocol::PING:
|
||||
connection->handlePacketPing(packet);
|
||||
break;
|
||||
|
||||
case protocol::PONG:
|
||||
connection->handlePacketPong(packet);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
logError(connection->virtual_server_id_, "{} Received hand decoded packet, but we've no method to handle it. Dropping packet.", connection->log_prefix());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceClientConnection::callback_command_decoded(void *ptr_this, ReassembledCommand *&command) {
|
||||
auto connection = reinterpret_cast<VoiceClientConnection*>(ptr_this);
|
||||
|
||||
/* we're exchanging the command so we're taking the ownership */
|
||||
connection->handlePacketCommand(std::exchange(command, nullptr));
|
||||
}
|
||||
|
||||
bool VoiceClientConnection::verify_encryption(const pipes::buffer_view &buffer /* incl. mac etc */) {
|
||||
ClientPacketParser packet_parser{buffer};
|
||||
if(!packet_parser.valid() || !packet_parser.is_encrypted()) return false;
|
||||
|
||||
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
|
||||
return this->crypt_handler.verify_encryption(buffer, packet_parser.packet_id(), this->incoming_generation_estimators[packet_parser.type()].generation());
|
||||
return this->packet_decoder_.verify_encryption(buffer);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) {
|
||||
if(this->client->state >= ConnectionState::DISCONNECTING || !this->client->getServer())
|
||||
return;
|
||||
|
||||
//TODO: Remove the buffer_execute_lock and use the one within the this->client->handlePacketCommand method
|
||||
unique_lock<std::recursive_timed_mutex> buffer_execute_lock;
|
||||
pipes::buffer payload{};
|
||||
uint16_t packet_id{};
|
||||
auto reexecute_handle = this->next_reassembled_command(buffer_execute_lock, payload, packet_id);
|
||||
|
||||
if(!payload.empty()){
|
||||
auto startTime = system_clock::now();
|
||||
try {
|
||||
this->client->handlePacketCommand(payload);
|
||||
} catch (std::exception& ex) {
|
||||
logCritical(this->client->getServerId(), "{} Exception reached root tree! {}", CLIENT_STR_LOG_PREFIX_(this->client), ex.what());
|
||||
}
|
||||
|
||||
auto end = system_clock::now();
|
||||
if(end - startTime > milliseconds(10)) {
|
||||
logError(this->client->getServerId(),
|
||||
"{} Handling of command packet needs more than 10ms ({}ms)",
|
||||
CLIENT_STR_LOG_PREFIX_(this->client),
|
||||
duration_cast<milliseconds>(end - startTime).count()
|
||||
);
|
||||
}
|
||||
}
|
||||
if(buffer_execute_lock.owns_lock())
|
||||
buffer_execute_lock.unlock();
|
||||
|
||||
auto voice_server = this->client->voice_server;
|
||||
if(voice_server && (reexecute_handle || this->should_reassembled_reschedule)) {
|
||||
should_reassembled_reschedule = false;
|
||||
this->client->voice_server->schedule_command_handling(this->client);
|
||||
}
|
||||
}
|
||||
|
||||
/* buffer_execute_lock: lock for in order execution */
|
||||
bool VoiceClientConnection::next_reassembled_command(unique_lock<std::recursive_timed_mutex>& buffer_execute_lock, pipes::buffer& result, uint16_t& packet_id) {
|
||||
command_fragment_buffer_t* buffer{nullptr};
|
||||
unique_lock<std::recursive_timed_mutex> buffer_lock; /* general buffer lock */
|
||||
|
||||
bool have_more{false};
|
||||
{
|
||||
//FIXME: Currently command low packets cant be handeled if there is a command packet stuck in reassamble
|
||||
|
||||
/* handle commands before command low packets */
|
||||
for(auto& buf : this->_command_fragment_buffers) {
|
||||
unique_lock ring_lock(buf.buffer_lock, try_to_lock); //Perm lock the buffer else, may command wount get handeled. Because we've more left, but say we waven't
|
||||
if(!ring_lock.owns_lock()) {
|
||||
this->should_reassembled_reschedule = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(buf.front_set()) {
|
||||
if(!buffer) { /* lets still test for reexecute */
|
||||
buffer_execute_lock = unique_lock(buf.execute_lock, try_to_lock);
|
||||
if(!buffer_execute_lock.owns_lock()) {
|
||||
this->should_reassembled_reschedule = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer_lock = move(ring_lock);
|
||||
buffer = &buf;
|
||||
} else {
|
||||
have_more = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!buffer)
|
||||
return false; /* we've no packets */
|
||||
|
||||
uint8_t packet_flags{0};
|
||||
pipes::buffer payload{};
|
||||
|
||||
/* lets find out if we've to reassemble the packet */
|
||||
auto& first_buffer = buffer->slot_value(0);
|
||||
packet_id = first_buffer.packet_id;
|
||||
if(first_buffer.packet_flags & PacketFlag::Fragmented) {
|
||||
uint16_t sequence_length{1};
|
||||
size_t total_payload_length{first_buffer.payload_length};
|
||||
do {
|
||||
if(sequence_length >= buffer->capacity()) {
|
||||
logError(this->client->getServerId(), "{} Command fragment buffer is full, and there is not fragmented packet end. Dropping full buffer which will probably cause a connection loss.", CLIENT_STR_LOG_PREFIX_(this->client));
|
||||
buffer->clear();
|
||||
return false; /* we've nothing to handle */
|
||||
}
|
||||
|
||||
if(!buffer->slot_set(sequence_length))
|
||||
return false; /* we need more packets */
|
||||
|
||||
auto& packet = buffer->slot_value(sequence_length++);
|
||||
total_payload_length += packet.payload_length;
|
||||
if(packet.packet_flags & PacketFlag::Fragmented) {
|
||||
/* yep we find the end */
|
||||
break;
|
||||
}
|
||||
} while(true);
|
||||
/* ok we have all fragments lets reassemble */
|
||||
|
||||
/*
|
||||
* Packet sequence could never be so long. If it is so then the data_length() returned an invalid value.
|
||||
* We're checking it here because we dont want to make a huge allocation
|
||||
*/
|
||||
assert(total_payload_length < 512 * 1024 * 1024);
|
||||
|
||||
pipes::buffer packet_buffer{total_payload_length};
|
||||
char* packet_buffer_ptr = &packet_buffer[0];
|
||||
size_t packet_count{0};
|
||||
|
||||
packet_flags = buffer->slot_value(0).packet_flags;
|
||||
while(packet_count < sequence_length) {
|
||||
auto fragment = buffer->pop_front();
|
||||
memcpy(packet_buffer_ptr, fragment.payload.data_ptr(), fragment.payload_length);
|
||||
|
||||
packet_buffer_ptr += fragment.payload_length;
|
||||
packet_count++;
|
||||
}
|
||||
|
||||
#ifndef _NDEBUG
|
||||
if((packet_buffer_ptr - 1) != &packet_buffer[packet_buffer.length() - 1]) {
|
||||
logCritical(this->client->getServer()->getServerId(),
|
||||
"Buffer over/underflow: packet_buffer_ptr != &packet_buffer[packet_buffer.length() - 1]; packet_buffer_ptr := {}; packet_buffer.end() := {}",
|
||||
(void*) packet_buffer_ptr,
|
||||
(void*) &packet_buffer[packet_buffer.length() - 1]
|
||||
);
|
||||
}
|
||||
#endif
|
||||
payload = packet_buffer;
|
||||
} else {
|
||||
auto packet = buffer->pop_front();
|
||||
packet_flags = packet.packet_flags;
|
||||
payload = packet.payload;
|
||||
}
|
||||
|
||||
have_more |= buffer->front_set(); /* set the more flag if we have more to process */
|
||||
buffer_lock.unlock();
|
||||
|
||||
if(packet_flags & PacketFlag::Compressed) {
|
||||
std::string error{};
|
||||
|
||||
auto decompressed_size = compression::qlz_decompressed_size(payload.data_ptr(), payload.length());
|
||||
if(decompressed_size == 0) {
|
||||
logTrace(this->client->getServerId(), "{} Failed to calculate decompressed size for received command. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
|
||||
return false;
|
||||
} else if(decompressed_size > 20 * 1024 * 1024) { /* max 20MB */
|
||||
logTrace(this->client->getServerId(), "{} Command packet has a too large compressed size. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
|
||||
return false;
|
||||
}
|
||||
auto decompress_buffer = buffer::allocate_buffer(decompressed_size);
|
||||
if(!compression::qlz_decompress_payload(payload.data_ptr(), decompress_buffer.data_ptr(), &decompressed_size)) {
|
||||
logTrace(this->client->getServerId(), "{} Failed to decompress received command. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
|
||||
return false;
|
||||
}
|
||||
|
||||
payload = decompress_buffer.range(0, decompressed_size);
|
||||
}
|
||||
|
||||
result = std::move(payload);
|
||||
return have_more;
|
||||
}
|
||||
|
||||
bool VoiceClientConnection::prepare_outgoing_packet(ts::protocol::OutgoingServerPacket *packet) {
|
||||
if(packet->type_and_flags & PacketFlag::Unencrypted) {
|
||||
this->crypt_handler.write_default_mac(packet->mac);
|
||||
} else {
|
||||
CryptHandler::key_t crypt_key{};
|
||||
CryptHandler::nonce_t crypt_nonce{};
|
||||
std::string error{};
|
||||
|
||||
if(!this->client->crypto.protocol_encrypted) {
|
||||
crypt_key = CryptHandler::default_key;
|
||||
crypt_nonce = CryptHandler::default_nonce;
|
||||
} else {
|
||||
if(!this->crypt_handler.generate_key_nonce(false, (uint8_t) packet->packet_type(), packet->packet_id(), packet->generation, crypt_key, crypt_nonce)) {
|
||||
logError(this->client->getServerId(), "{} Failed to generate crypt key/nonce for sending a packet. This should never happen! Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto crypt_result = this->crypt_handler.encrypt((char*) packet->packet_data() + ServerPacketP::kHeaderOffset, ServerPacketP::kHeaderLength,
|
||||
packet->payload, packet->payload_size,
|
||||
packet->mac,
|
||||
crypt_key, crypt_nonce, error);
|
||||
if(!crypt_result){
|
||||
logError(this->client->getServerId(), "{} Failed to encrypt packet. Error: {}", CLIENT_STR_LOG_PREFIX_(this->client), error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
VoiceClientConnection::WBufferPopResult VoiceClientConnection::pop_write_buffer(protocol::OutgoingServerPacket *&result) {
|
||||
if(this->client->state == ConnectionState::DISCONNECTED)
|
||||
return WBufferPopResult::DRAINED;
|
||||
|
||||
bool need_prepare_packet{false}, more_packets{false};
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
if(this->resend_queue_head) {
|
||||
result = this->resend_queue_head;
|
||||
if(result->next) {
|
||||
assert(this->resend_queue_tail != &result->next);
|
||||
this->resend_queue_head = result->next;
|
||||
} else {
|
||||
assert(this->resend_queue_tail == &result->next);
|
||||
this->resend_queue_head = nullptr;
|
||||
this->resend_queue_tail = &this->resend_queue_head;
|
||||
}
|
||||
} else if(this->write_queue_head) {
|
||||
result = this->write_queue_head;
|
||||
if(result->next) {
|
||||
assert(this->write_queue_tail != &result->next);
|
||||
this->write_queue_head = result->next;
|
||||
} else {
|
||||
assert(this->write_queue_tail == &result->next);
|
||||
this->write_queue_head = nullptr;
|
||||
this->write_queue_tail = &this->write_queue_head;
|
||||
}
|
||||
need_prepare_packet = true;
|
||||
} else {
|
||||
return WBufferPopResult::DRAINED;
|
||||
}
|
||||
result->next = nullptr;
|
||||
more_packets = this->resend_queue_head != nullptr || this->write_queue_head != nullptr;
|
||||
}
|
||||
|
||||
if(need_prepare_packet)
|
||||
this->prepare_outgoing_packet(result);
|
||||
return more_packets ? WBufferPopResult::MORE_AVAILABLE : WBufferPopResult::DRAINED;
|
||||
}
|
||||
|
||||
void VoiceClientConnection::execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next) {
|
||||
std::deque<std::shared_ptr<connection::AcknowledgeManager::Entry>> buffers{};
|
||||
std::string error{};
|
||||
|
||||
if (this->acknowledge_handler.execute_resend(now, next, buffers, error) < 0) {
|
||||
debugMessage(client->getServerId(), "{} Failed to execute packet resend: {}", CLIENT_STR_LOG_PREFIX_(this->client), error);
|
||||
|
||||
if(this->client->state == ConnectionState::CONNECTED) {
|
||||
this->client->disconnect(ViewReasonId::VREASON_TIMEOUT, config::messages::timeout::packet_resend_failed, nullptr, true);
|
||||
} else {
|
||||
this->client->close_connection(system_clock::now() + seconds(1));
|
||||
}
|
||||
} else if(!buffers.empty()) {
|
||||
size_t send_count{0};
|
||||
{
|
||||
lock_guard wlock{this->write_queue_mutex};
|
||||
for(auto& buffer : buffers) {
|
||||
auto packet = (protocol::OutgoingServerPacket*) buffer->packet_ptr;
|
||||
if(packet->next) continue; /* still in write queue (this shall not happen very often) */
|
||||
if(&packet->next == this->write_queue_tail || &packet->next == this->resend_queue_tail) continue;
|
||||
|
||||
packet->ref(); /* for the write queue again */
|
||||
*this->resend_queue_tail = packet;
|
||||
this->resend_queue_tail = &packet->next;
|
||||
|
||||
send_count++;
|
||||
buffer->resend_count++;
|
||||
this->packet_statistics().send_command((protocol::PacketType) buffer->packet_type, buffer->packet_full_id);
|
||||
}
|
||||
}
|
||||
logTrace(client->getServerId(), "{} Resending {} packets. Send actually {} packets.", CLIENT_STR_LOG_PREFIX_(client), buffers.size(), send_count);
|
||||
this->triggerWrite();
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceClientConnection::encrypt_write_queue() {
|
||||
OutgoingServerPacket* packets_head, *packets_tail;
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
packets_head = this->write_queue_head;
|
||||
this->write_queue_head = nullptr;
|
||||
this->write_queue_tail = &this->write_queue_head;
|
||||
}
|
||||
if(!packets_head) return;
|
||||
|
||||
auto packet = packets_head;
|
||||
while(packet) {
|
||||
this->prepare_outgoing_packet(packet);
|
||||
packets_tail = packet;
|
||||
packet = packet->next;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
*this->resend_queue_tail = packets_head;
|
||||
this->resend_queue_tail = &packets_tail->next;
|
||||
}
|
||||
std::shared_ptr<ts::server::VoiceClient> VoiceClientConnection::getCurrentClient() {
|
||||
if(!this->current_client) return nullptr;
|
||||
return std::dynamic_pointer_cast<server::VoiceClient>(this->current_client->ref());
|
||||
}
|
||||
|
||||
bool VoiceClientConnection::wait_empty_write_and_prepare_queue(chrono::time_point<chrono::system_clock> until) {
|
||||
while(true) {
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
if(this->write_queue_head)
|
||||
goto _wait;
|
||||
|
||||
if(this->resend_queue_head)
|
||||
goto _wait;
|
||||
}
|
||||
break;
|
||||
|
||||
_wait:
|
||||
if(until.time_since_epoch().count() != 0 && system_clock::now() > until)
|
||||
return false;
|
||||
|
||||
threads::self::sleep_for(milliseconds(5));
|
||||
}
|
||||
return true;
|
||||
return this->packet_encoder_.wait_empty_write_and_prepare_queue(until);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::reset() {
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
auto head = this->write_queue_head;
|
||||
while(head) {
|
||||
auto next = head->next;
|
||||
head->unref();
|
||||
head = next;
|
||||
}
|
||||
this->write_queue_head = nullptr;
|
||||
this->write_queue_tail = &this->write_queue_head;
|
||||
|
||||
head = this->resend_queue_head;
|
||||
while(head) {
|
||||
auto next = head->next;
|
||||
head->unref();
|
||||
head = next;
|
||||
}
|
||||
this->resend_queue_head = nullptr;
|
||||
this->resend_queue_tail = &this->resend_queue_head;
|
||||
}
|
||||
|
||||
this->acknowledge_handler.reset();
|
||||
this->crypt_handler.reset();
|
||||
this->packet_id_manager.reset();
|
||||
|
||||
{
|
||||
lock_guard buffer_lock(this->packet_buffer_lock);
|
||||
for(auto& buffer : this->_command_fragment_buffers)
|
||||
buffer.reset();
|
||||
}
|
||||
this->ping_handler_.reset();
|
||||
this->packet_decoder_.reset();
|
||||
this->packet_encoder_.reset();
|
||||
}
|
||||
|
||||
void VoiceClientConnection::force_insert_command(const pipes::buffer_view &buffer) {
|
||||
CommandFragment fragment_entry{
|
||||
0,
|
||||
0,
|
||||
|
||||
PacketFlag::Unencrypted,
|
||||
(uint32_t) buffer.length(),
|
||||
buffer.own_buffer()
|
||||
};
|
||||
|
||||
|
||||
{
|
||||
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
|
||||
unique_lock queue_lock(fragment_buffer.buffer_lock);
|
||||
fragment_buffer.push_front(std::move(fragment_entry));
|
||||
}
|
||||
|
||||
auto voice_server = this->client->voice_server;
|
||||
if(voice_server)
|
||||
voice_server->schedule_command_handling(this->client);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::register_initiv_packet() {
|
||||
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
|
||||
unique_lock buffer_lock(fragment_buffer.buffer_lock);
|
||||
fragment_buffer.set_full_index_to(1); /* the first packet (0) is already the clientinitiv packet */
|
||||
}
|
||||
|
||||
void VoiceClientConnection::send_packet(protocol::OutgoingServerPacket *packet) {
|
||||
uint32_t full_id;
|
||||
{
|
||||
std::lock_guard id_lock{this->packet_id_mutex};
|
||||
full_id = this->packet_id_manager.generate_full_id(packet->packet_type());
|
||||
}
|
||||
packet->set_packet_id(full_id & 0xFFFFU);
|
||||
packet->generation = full_id >> 16U;
|
||||
|
||||
{
|
||||
std::lock_guard qlock{this->write_queue_mutex};
|
||||
*this->write_queue_tail = packet;
|
||||
this->write_queue_tail = &packet->next;
|
||||
}
|
||||
|
||||
auto statistics = this->client ? this->client->connectionStatistics : nullptr;
|
||||
if(statistics) {
|
||||
auto category = stats::ConnectionStatistics::category::from_type(packet->packet_type());
|
||||
statistics->logOutgoingPacket(category, packet->packet_length() + 96); /* 96 for the UDP packet overhead */
|
||||
}
|
||||
|
||||
this->triggerWrite();
|
||||
void VoiceClientConnection::reset_remote_address() {
|
||||
memset(&this->remote_address_, 0, sizeof(this->remote_address_));
|
||||
memset(&this->remote_address_info_, 0, sizeof(this->remote_address_info_));
|
||||
}
|
||||
|
||||
void VoiceClientConnection::send_packet(protocol::PacketType type, protocol::PacketFlag::PacketFlags flag, const void *payload, size_t payload_size) {
|
||||
auto packet = protocol::allocate_outgoing_packet(payload_size);
|
||||
|
||||
packet->type_and_flags = (uint8_t) type | (uint8_t) flag;
|
||||
memcpy(packet->payload, payload, payload_size);
|
||||
|
||||
this->send_packet(packet);
|
||||
this->packet_encoder_.send_packet(type, flag, payload, payload_size);
|
||||
}
|
||||
|
||||
#define MAX_COMMAND_PACKET_PAYLOAD_LENGTH (487)
|
||||
void VoiceClientConnection::send_command(const std::string_view &command, bool low, std::unique_ptr<threads::Future<bool>> ack_listener) {
|
||||
bool own_data_buffer{false};
|
||||
void* own_data_buffer_ptr; /* imutable! */
|
||||
void VoiceClientConnection::send_command(const std::string_view &cmd, bool b, std::unique_ptr<threads::Future<bool>> cb) {
|
||||
this->packet_encoder_.send_command(cmd, b, std::move(cb));
|
||||
}
|
||||
|
||||
const char* data_buffer{command.data()};
|
||||
size_t data_length{command.length()};
|
||||
void VoiceClientConnection::callback_encode_crypt_error(void *ptr_this,
|
||||
const PacketEncoder::CryptError &error,
|
||||
const std::string &detail) {
|
||||
auto connection = reinterpret_cast<VoiceClientConnection*>(ptr_this);
|
||||
switch (error) {
|
||||
case PacketEncoder::CryptError::ENCRYPT_FAILED:
|
||||
logError(connection->virtual_server_id_, "{} Failed to encrypt packet. Error: {}", connection->log_prefix(), detail);
|
||||
break;
|
||||
|
||||
uint8_t head_pflags{0};
|
||||
PacketType ptype{low ? PacketType::COMMAND_LOW : PacketType::COMMAND};
|
||||
protocol::OutgoingServerPacket *packets_head{nullptr};
|
||||
protocol::OutgoingServerPacket **packets_tail{&packets_head};
|
||||
case PacketEncoder::CryptError::KEY_GENERATION_FAILED:
|
||||
logError(connection->virtual_server_id_, "{} Failed to generate crypt key/nonce for sending a packet. This should never happen! Dropping packet.", connection->log_prefix());
|
||||
break;
|
||||
|
||||
/* only compress "long" commands */
|
||||
if(command.size() > 100) {
|
||||
size_t max_compressed_payload_size = compression::qlz_compressed_size(command.data(), command.length());
|
||||
auto compressed_buffer = ::malloc(max_compressed_payload_size);
|
||||
|
||||
size_t compressed_size{max_compressed_payload_size};
|
||||
if(!compression::qlz_compress_payload(command.data(), command.length(), compressed_buffer, &compressed_size)) {
|
||||
logCritical(0, "Failed to compress command packet. Dropping packet");
|
||||
::free(compressed_buffer);
|
||||
default:
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
/* we don't need to make the command longer than it is */
|
||||
if(compressed_size < command.length() || this->client->getType() == ClientType::CLIENT_TEAMSPEAK) { /* TS3 requires each splituped packet to be compressed (Update: Not 100% sure since there was another bug when discovering this but I've kept it since) */
|
||||
own_data_buffer = true;
|
||||
data_buffer = (char*) compressed_buffer;
|
||||
own_data_buffer_ptr = compressed_buffer;
|
||||
data_length = compressed_size;
|
||||
head_pflags |= PacketFlag::Compressed;
|
||||
} else {
|
||||
::free(compressed_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ptype_and_flags{(uint8_t) ((uint8_t) ptype | (uint8_t) PacketFlag::NewProtocol)};
|
||||
if(data_length > MAX_COMMAND_PACKET_PAYLOAD_LENGTH) {
|
||||
auto chunk_count = (size_t) ceil((float) data_length / (float) MAX_COMMAND_PACKET_PAYLOAD_LENGTH);
|
||||
auto chunk_size = (size_t) ceil((float) data_length / (float) chunk_count);
|
||||
void VoiceClientConnection::callback_request_write(void *ptr_this) {
|
||||
auto connection = reinterpret_cast<VoiceClientConnection*>(ptr_this);
|
||||
connection->triggerWrite();
|
||||
}
|
||||
|
||||
while(true) {
|
||||
auto bytes = min(chunk_size, data_length);
|
||||
auto packet = protocol::allocate_outgoing_packet(bytes);
|
||||
packet->type_and_flags = ptype_and_flags;
|
||||
memcpy(packet->payload, data_buffer, bytes);
|
||||
void VoiceClientConnection::callback_resend_failed(void *ptr_this, const shared_ptr<AcknowledgeManager::Entry> &entry) {
|
||||
auto connection = reinterpret_cast<VoiceClientConnection*>(ptr_this);
|
||||
|
||||
*packets_tail = packet;
|
||||
packets_tail = &packet->next;
|
||||
|
||||
data_length -= bytes;
|
||||
if(data_length == 0) {
|
||||
packet->type_and_flags |= PacketFlag::Fragmented;
|
||||
break;
|
||||
}
|
||||
data_buffer += bytes;
|
||||
}
|
||||
packets_head->type_and_flags |= PacketFlag::Fragmented;
|
||||
debugMessage(connection->virtual_server_id_, "{} Failed to execute packet resend of packet {}. Dropping connection.", connection->log_prefix(), entry->packet_full_id);
|
||||
auto client = connection->getCurrentClient();
|
||||
assert(client); /* TIXME! */
|
||||
if(client->state == ConnectionState::CONNECTED) {
|
||||
client->disconnect(ViewReasonId::VREASON_TIMEOUT, config::messages::timeout::packet_resend_failed, nullptr, true);
|
||||
} else {
|
||||
auto packet = protocol::allocate_outgoing_packet(data_length);
|
||||
packet->type_and_flags = ptype_and_flags;
|
||||
|
||||
memcpy(packet->payload, data_buffer, data_length);
|
||||
|
||||
packets_head = packet;
|
||||
packets_tail = &packet->next;
|
||||
client->close_connection(system_clock::now() + seconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceClientConnection::callback_resend_statistics(void *ptr_this, size_t send_count) {
|
||||
auto connection = reinterpret_cast<VoiceClientConnection*>(ptr_this);
|
||||
|
||||
{
|
||||
std::lock_guard id_lock{this->packet_id_mutex};
|
||||
uint32_t full_id;
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
full_id = this->packet_id_manager.generate_full_id(ptype);
|
||||
logTrace(connection->virtual_server_id_, "{} Resending {} packets.", connection->log_prefix(), send_count);
|
||||
}
|
||||
|
||||
head->set_packet_id(full_id & 0xFFFFU);
|
||||
head->generation = full_id >> 16U;
|
||||
head = head->next;
|
||||
}
|
||||
}
|
||||
packets_head->type_and_flags |= head_pflags;
|
||||
void VoiceClientConnection::callback_outgoing_connection_statistics(void *ptr_this,
|
||||
ts::stats::ConnectionStatistics::category::value category,
|
||||
size_t send_count) {
|
||||
auto connection = reinterpret_cast<VoiceClientConnection*>(ptr_this);
|
||||
auto client = connection->getCurrentClient();
|
||||
if(!client) return;
|
||||
|
||||
/* do this before the next ptr might get modified due to the write queue */
|
||||
auto statistics = this->client ? this->client->connectionStatistics : nullptr;
|
||||
/* general stats */
|
||||
if(statistics) {
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
statistics->logOutgoingPacket(stats::ConnectionStatistics::category::COMMAND, head->packet_length() + 96); /* 96 for the UDP overhead */
|
||||
head = head->next;
|
||||
}
|
||||
}
|
||||
auto statistics = client->connectionStatistics;
|
||||
if(!statistics) return;
|
||||
|
||||
/* loss stats */
|
||||
{
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
auto full_packet_id = (uint32_t) (head->generation << 16U) | head->packet_id();
|
||||
this->packet_statistics_.send_command(head->packet_type(), full_packet_id);
|
||||
statistics->logOutgoingPacket(category, send_count);
|
||||
}
|
||||
|
||||
/* increase a reference for the ack handler */
|
||||
head->ref();
|
||||
void VoiceClientConnection::callback_ping_send(void *ptr_this, uint16_t &id) {
|
||||
auto connection = reinterpret_cast<VoiceClientConnection*>(ptr_this);
|
||||
|
||||
/* Even thou the packet is yet unencrypted, it will be encrypted with the next write. The next write will be before the next resend because the next ptr must be null in order to resend a packet */
|
||||
if(&head->next == packets_tail)
|
||||
this->acknowledge_handler.process_packet(ptype, full_packet_id, head, std::move(ack_listener));
|
||||
else
|
||||
this->acknowledge_handler.process_packet(ptype, full_packet_id, head, nullptr);
|
||||
auto packet = protocol::allocate_outgoing_packet(0);
|
||||
packet->ref();
|
||||
|
||||
head = head->next;
|
||||
}
|
||||
}
|
||||
packet->type_and_flags = (uint8_t) PacketType::PING | (uint8_t) PacketFlag::Unencrypted;
|
||||
connection->packet_encoder_.send_packet(packet);
|
||||
id = packet->packet_id();
|
||||
|
||||
{
|
||||
std::lock_guard qlock{this->write_queue_mutex};
|
||||
*this->write_queue_tail = packets_head;
|
||||
this->write_queue_tail = packets_tail;
|
||||
}
|
||||
this->triggerWrite();
|
||||
packet->unref();
|
||||
}
|
||||
|
||||
if(own_data_buffer)
|
||||
::free(own_data_buffer_ptr);
|
||||
void VoiceClientConnection::callback_ping_send_recovery(void *ptr_this) {
|
||||
auto connection = reinterpret_cast<VoiceClientConnection*>(ptr_this);
|
||||
|
||||
connection->send_command("notifyconnectioninforequest invokerids=0", false, nullptr);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::callback_ping_timeout(void *ptr_this) {
|
||||
(void) ptr_this;
|
||||
/* doing nothing a packet resend failed will cause the client to disconnect */
|
||||
}
|
||||
@@ -16,6 +16,11 @@
|
||||
#include "protocol/AcknowledgeManager.h"
|
||||
#include <protocol/generation.h>
|
||||
#include "./PacketStatistics.h"
|
||||
#include "./PacketDecoder.h"
|
||||
#include "./PacketEncoder.h"
|
||||
#include "./ServerCommandExecutor.h"
|
||||
#include "CryptSetupHandler.h"
|
||||
#include "PingHandler.h"
|
||||
|
||||
//#define LOG_ACK_SYSTEM
|
||||
#ifdef LOG_ACK_SYSTEM
|
||||
@@ -38,111 +43,85 @@ namespace ts {
|
||||
friend class server::VoiceServer;
|
||||
friend class server::VoiceClient;
|
||||
friend class server::POWHandler;
|
||||
|
||||
using PacketDecoder = server::server::udp::PacketDecoder;
|
||||
using PacketEncoder = server::server::udp::PacketEncoder;
|
||||
using PingHandler = server::server::udp::PingHandler;
|
||||
using CryptSetupHandler = server::server::udp::CryptSetupHandler;
|
||||
using ReassembledCommand = server::server::udp::ReassembledCommand;
|
||||
|
||||
using StatisticsCategory = stats::ConnectionStatistics::category;
|
||||
public:
|
||||
enum struct WBufferPopResult {
|
||||
DRAINED,
|
||||
MORE_AVAILABLE
|
||||
};
|
||||
|
||||
struct CommandFragment {
|
||||
uint16_t packet_id{0};
|
||||
uint16_t packet_generation{0};
|
||||
|
||||
uint8_t packet_flags{0};
|
||||
uint32_t payload_length : 24;
|
||||
pipes::buffer payload{};
|
||||
|
||||
CommandFragment() { this->payload_length = 0; }
|
||||
CommandFragment(uint16_t packetId, uint16_t packetGeneration, uint8_t packetFlags, uint32_t payloadLength, pipes::buffer payload) : packet_id{packetId}, packet_generation{packetGeneration}, packet_flags{packetFlags},
|
||||
payload_length{payloadLength}, payload{std::move(payload)} {}
|
||||
|
||||
CommandFragment& operator=(const CommandFragment&) = default;
|
||||
CommandFragment(const CommandFragment& other) = default;
|
||||
CommandFragment(CommandFragment&&) = default;
|
||||
};
|
||||
static_assert(sizeof(CommandFragment) == 8 + sizeof(pipes::buffer));
|
||||
|
||||
typedef protocol::PacketRingBuffer<CommandFragment, 32, CommandFragment> command_fragment_buffer_t;
|
||||
typedef std::array<command_fragment_buffer_t, 2> command_packet_reassembler;
|
||||
|
||||
explicit VoiceClientConnection(server::VoiceClient*);
|
||||
virtual ~VoiceClientConnection();
|
||||
|
||||
/* Do not send command packets via send_packet! The send_packet will take ownership of the packet! */
|
||||
void send_packet(protocol::OutgoingServerPacket* /* packet */);
|
||||
void send_packet(protocol::PacketType /* type */, protocol::PacketFlag::PacketFlags /* flags */, const void* /* payload */, size_t /* payload length */);
|
||||
void send_command(const std::string_view& /* build command command */, bool /* command low */, std::unique_ptr<threads::Future<bool>> /* acknowledge listener */);
|
||||
|
||||
CryptHandler* getCryptHandler(){ return &crypt_handler; }
|
||||
|
||||
server::VoiceClient* getClient(){ return client; }
|
||||
std::shared_ptr<server::VoiceClient> getCurrentClient();
|
||||
|
||||
#ifdef VC_USE_READ_QUEUE
|
||||
bool handleNextDatagram();
|
||||
#endif
|
||||
/* if the result is true, ownership has been transferred */
|
||||
WBufferPopResult pop_write_buffer(protocol::OutgoingServerPacket*& /* packet */);
|
||||
void execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next);
|
||||
|
||||
void encrypt_write_queue();
|
||||
bool wait_empty_write_and_prepare_queue(std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
|
||||
|
||||
protocol::PacketIdManager& getPacketIdManager() { return this->packet_id_manager; }
|
||||
AcknowledgeManager& getAcknowledgeManager() { return this->acknowledge_handler; }
|
||||
inline auto& get_incoming_generation_estimators() { return this->incoming_generation_estimators; }
|
||||
void reset();
|
||||
void reset_remote_address();
|
||||
|
||||
void force_insert_command(const pipes::buffer_view& /* payload */);
|
||||
void register_initiv_packet();
|
||||
[[nodiscard]] std::string log_prefix();
|
||||
|
||||
[[nodiscard]] inline auto virtual_server_id() const { return this->virtual_server_id_; }
|
||||
|
||||
[[nodiscard]] inline const auto& remote_address() const { return this->remote_address_; }
|
||||
[[nodiscard]] inline const auto& socket_id() const { return this->socket_id_; }
|
||||
|
||||
[[nodiscard]] inline auto& packet_statistics() { return this->packet_statistics_; }
|
||||
//buffer::SortedBufferQueue<protocol::ClientPacket>** getReadQueue() { return this->readTypedQueue; }
|
||||
[[nodiscard]] inline auto& packet_decoder() { return this->packet_decoder_; }
|
||||
[[nodiscard]] inline auto& packet_encoder() { return this->packet_encoder_; }
|
||||
|
||||
[[nodiscard]] inline auto& ping_handler() { return this->ping_handler_; }
|
||||
[[nodiscard]] inline auto& crypt_setup_handler() { return this->crypt_setup_handler_; }
|
||||
protected:
|
||||
void handle_incoming_datagram(const pipes::buffer_view &buffer);
|
||||
bool verify_encryption(const pipes::buffer_view& /* full packet */);
|
||||
|
||||
void triggerWrite();
|
||||
private:
|
||||
server::VoiceClient* client = nullptr;
|
||||
ServerId virtual_server_id_;
|
||||
server::VoiceClient* current_client;
|
||||
|
||||
int socket_id_{0};
|
||||
sockaddr_storage remote_address_{};
|
||||
server::udp::pktinfo_storage remote_address_info_{};
|
||||
|
||||
//Decryption / encryption stuff
|
||||
CryptHandler crypt_handler; /* access to CryptHandler is thread save */
|
||||
CompressionHandler compress_handler;
|
||||
AcknowledgeManager acknowledge_handler;
|
||||
|
||||
std::atomic_bool should_reassembled_reschedule{}; /* this get checked as soon the command handle lock has been released so trylock will succeed */
|
||||
|
||||
//Handle stuff
|
||||
void execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */);
|
||||
bool next_reassembled_command(std::unique_lock<std::recursive_timed_mutex> &buffer_execute_lock /* packet channel execute lock */, pipes::buffer & /* buffer*/, uint16_t& /* packet id */);
|
||||
|
||||
|
||||
/* ---------- Write ---------- */
|
||||
spin_mutex write_queue_mutex{};
|
||||
protocol::OutgoingServerPacket* resend_queue_head{nullptr};
|
||||
protocol::OutgoingServerPacket** resend_queue_tail{&resend_queue_head};
|
||||
|
||||
protocol::OutgoingServerPacket* write_queue_head{nullptr};
|
||||
protocol::OutgoingServerPacket** write_queue_tail{&write_queue_head};
|
||||
|
||||
/* ---------- Processing ---------- */
|
||||
/* automatically locked because packets of the same kind should be lock their "work_lock" from their WritePreprocessQueue object */
|
||||
protocol::PacketIdManager packet_id_manager;
|
||||
spin_mutex packet_id_mutex{};
|
||||
|
||||
/* this function is thread save :) */
|
||||
std::atomic<uint8_t> prepare_process_count{0}; /* current thread count preparing a packet */
|
||||
bool prepare_outgoing_packet(protocol::OutgoingServerPacket* /* packet */);
|
||||
|
||||
std::array<protocol::generation_estimator, 9> incoming_generation_estimators{}; /* implementation is thread save */
|
||||
std::recursive_mutex packet_buffer_lock;
|
||||
command_packet_reassembler _command_fragment_buffers;
|
||||
|
||||
static inline uint8_t command_fragment_buffer_index(uint8_t packet_index) {
|
||||
return packet_index & 0x1U; /* use 0 for command and 1 for command low */
|
||||
}
|
||||
|
||||
server::client::PacketStatistics packet_statistics_{};
|
||||
|
||||
PacketDecoder packet_decoder_;
|
||||
PacketEncoder packet_encoder_;
|
||||
|
||||
CryptSetupHandler crypt_setup_handler_;
|
||||
PingHandler ping_handler_{};
|
||||
|
||||
static void callback_packet_decoded(void*, const protocol::ClientPacketParser&);
|
||||
static void callback_command_decoded(void*, ReassembledCommand*&);
|
||||
static void callback_send_acknowledge(void*, uint16_t, bool);
|
||||
static void callback_request_write(void*);
|
||||
static void callback_encode_crypt_error(void*, const PacketEncoder::CryptError&, const std::string&);
|
||||
static void callback_resend_failed(void*, const std::shared_ptr<AcknowledgeManager::Entry>&);
|
||||
static void callback_resend_statistics(void*, size_t);
|
||||
static void callback_outgoing_connection_statistics(void*, StatisticsCategory::value, size_t /* bytes */);
|
||||
static void callback_ping_send(void*, uint16_t&);
|
||||
static void callback_ping_send_recovery(void*);
|
||||
static void callback_ping_timeout(void*);
|
||||
|
||||
/* Attention: All packet callbacks are called from the IO threads and are not thread save! */
|
||||
void handlePacketCommand(ReassembledCommand* /* command */); /* The ownership will be transferred */
|
||||
void handlePacketAck(const protocol::ClientPacketParser&);
|
||||
void handlePacketAckLow(const protocol::ClientPacketParser&);
|
||||
void handlePacketVoice(const protocol::ClientPacketParser&);
|
||||
void handlePacketVoiceWhisper(const protocol::ClientPacketParser&);
|
||||
void handlePacketPing(const protocol::ClientPacketParser&);
|
||||
void handlePacketPong(const protocol::ClientPacketParser&);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#include <tommath.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "../web/WebClient.h"
|
||||
#include "VoiceClient.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts::connection;
|
||||
using namespace ts::protocol;
|
||||
|
||||
void VoiceClientConnection::handlePacketPong(const ts::protocol::ClientPacketParser &packet) {
|
||||
if(packet.payload_length() < 2) return;
|
||||
|
||||
this->ping_handler_.received_pong(be2le16((char*) packet.payload().data_ptr()));
|
||||
}
|
||||
|
||||
void VoiceClientConnection::handlePacketPing(const protocol::ClientPacketParser& packet) {
|
||||
#ifdef PKT_LOG_PING
|
||||
logMessage(this->getServerId(), "{}[Ping] Sending pong for client requested ping {}", CLIENT_STR_LOG_PREFIX, packet->packetId());
|
||||
#endif
|
||||
char buffer[2];
|
||||
le2be16(packet.packet_id(), buffer);
|
||||
this->send_packet(PacketType::PONG, PacketFlag::Unencrypted, buffer, 2);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::handlePacketVoice(const protocol::ClientPacketParser& packet) {
|
||||
auto client = this->getCurrentClient();
|
||||
if(!client) return;
|
||||
|
||||
client->handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::handlePacketVoiceWhisper(const ts::protocol::ClientPacketParser &packet) {
|
||||
auto client = this->getCurrentClient();
|
||||
if(!client) return;
|
||||
|
||||
client->handlePacketVoiceWhisper(packet.payload(), (packet.flags() & PacketFlag::NewProtocol) > 0, (packet.flags() & PacketFlag::Compressed) > 0);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::handlePacketAck(const protocol::ClientPacketParser& packet) {
|
||||
if(packet.payload_length() < 2) return;
|
||||
uint16_t target_id{be2le16(packet.payload().data_ptr<char>())};
|
||||
|
||||
this->ping_handler_.received_command_acknowledged();
|
||||
this->packet_statistics().received_acknowledge((protocol::PacketType) packet.type(), target_id | (uint32_t) (packet.estimated_generation() << 16U));
|
||||
|
||||
string error{};
|
||||
if(!this->packet_encoder().acknowledge_manager().process_acknowledge(packet.type(), target_id, error))
|
||||
debugMessage(this->virtual_server_id_, "{} Failed to handle acknowledge: {}", this->log_prefix(), error);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::handlePacketAckLow(const ts::protocol::ClientPacketParser &packet) {
|
||||
this->handlePacketAck(packet);
|
||||
}
|
||||
|
||||
void VoiceClientConnection::handlePacketCommand(ReassembledCommand* command) {
|
||||
{
|
||||
using CommandHandleResult = CryptSetupHandler::CommandHandleResult ;
|
||||
|
||||
auto result = this->crypt_setup_handler_.handle_command(command->command_view());
|
||||
switch (result) {
|
||||
case CommandHandleResult::PASS_THROUGH:
|
||||
break;
|
||||
|
||||
case CommandHandleResult::CONSUME_COMMAND:
|
||||
return;
|
||||
|
||||
case CommandHandleResult::CLOSE_CONNECTION:
|
||||
auto client = this->getCurrentClient();
|
||||
assert(client); /* FIXME! */
|
||||
client->close_connection(std::chrono::system_clock::time_point{});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto client = this->getCurrentClient();
|
||||
if(!client) {
|
||||
/* TODO! */
|
||||
return;
|
||||
}
|
||||
|
||||
client->server_command_executor().enqueue_command_execution(command);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <tommath.h>
|
||||
#include <tomcrypt.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <misc/digest.h>
|
||||
#include <misc/base64.h>
|
||||
#include <openssl/sha.h>
|
||||
@@ -9,190 +8,9 @@
|
||||
|
||||
#include "VoiceClient.h"
|
||||
#include "../../InstanceHandler.h"
|
||||
#include "../../server/VoiceServer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts::server;
|
||||
using namespace ts::protocol;
|
||||
using namespace ts::connection;
|
||||
|
||||
inline void generate_random(uint8_t *destination, size_t length) {
|
||||
while(length-- > 0)
|
||||
*(destination++) = (uint8_t) rand();
|
||||
}
|
||||
|
||||
ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) {
|
||||
this->last_packet_handshake = system_clock::now();
|
||||
|
||||
std::unique_lock state_lock{this->state_lock};
|
||||
if(this->state == ConnectionState::CONNECTED) { /* we've a reconnect */
|
||||
if(system_clock::now() - this->lastPingResponse < seconds(5)) {
|
||||
logMessage(this->getServerId(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
|
||||
CLIENT_STR_LOG_PREFIX,
|
||||
duration_cast<milliseconds>(system_clock::now() - this->lastPingResponse).count()
|
||||
);
|
||||
return ts::command_result{error::ok};
|
||||
} else if(!config::voice::allow_session_reinitialize) {
|
||||
logMessage(this->getServerId(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings",
|
||||
CLIENT_STR_LOG_PREFIX,
|
||||
duration_cast<milliseconds>(system_clock::now() - this->lastPingResponse).count()
|
||||
);
|
||||
return ts::command_result{error::ok};
|
||||
}
|
||||
logMessage(this->getServerId(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt",
|
||||
CLIENT_STR_LOG_PREFIX,
|
||||
duration_cast<milliseconds>(system_clock::now() - this->lastPingResponse).count()
|
||||
);
|
||||
|
||||
state_lock.unlock();
|
||||
|
||||
{
|
||||
unique_lock server_channel_lock(this->server->channel_tree_lock); /* we cant get moved if this is locked! */
|
||||
if(this->currentChannel)
|
||||
this->server->client_move(this->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock);
|
||||
}
|
||||
|
||||
this->finalDisconnect();
|
||||
state_lock.lock();
|
||||
} else if(this->state >= ConnectionState::DISCONNECTING) {
|
||||
state_lock.unlock();
|
||||
std::shared_lock disconnect_finish{this->finalDisconnectLock}; /* await until the last disconnect has been processed */
|
||||
state_lock.lock();
|
||||
this->state = ConnectionState::INIT_HIGH;
|
||||
} else if(this->state == ConnectionState::INIT_HIGH) {
|
||||
logTrace(this->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX);
|
||||
return command_result{error::ok};
|
||||
} else {
|
||||
this->state = ConnectionState::INIT_HIGH;
|
||||
}
|
||||
state_lock.unlock();
|
||||
|
||||
this->connection->reset();
|
||||
this->connection->register_initiv_packet();
|
||||
this->connection->packet_statistics().reset_offsets();
|
||||
this->crypto.protocol_encrypted = false;
|
||||
|
||||
bool use_teaspeak = command.hasParm("teaspeak");
|
||||
if(use_teaspeak ? !config::server::clients::teaspeak : !config::server::clients::teamspeak)
|
||||
return command_result{error::client_type_is_not_allowed};
|
||||
|
||||
if(use_teaspeak) {
|
||||
debugMessage(this->getServerId(), "{} Client using TeaSpeak with auth type {}", CLIENT_STR_LOG_PREFIX, command["verify_type"].string());
|
||||
this->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_TEASPEAK;
|
||||
}
|
||||
|
||||
/* normal TeamSpeak handling */
|
||||
string clientAlpha = base64::decode(command["alpha"]); //random
|
||||
if(clientAlpha.length() != 10) return ts::command_result{error::parameter_invalid};
|
||||
|
||||
string clientOmega = base64::decode(command["omega"]); //The identity public key
|
||||
string ip = command["ip"];
|
||||
bool ot = command[0].has("ot") ? command["ot"] : false;
|
||||
|
||||
this->crypto.remote_key = std::shared_ptr<ecc_key>(new ecc_key{}, [](ecc_key* key){
|
||||
if(!key) return;
|
||||
ecc_free(key);
|
||||
delete key;
|
||||
});
|
||||
|
||||
auto state = ecc_import((const unsigned char *) clientOmega.data(), clientOmega.length(), this->crypto.remote_key.get());
|
||||
if(state != CRYPT_OK) {
|
||||
this->crypto.remote_key.reset();
|
||||
return ts::command_result{error::client_could_not_validate_identity};
|
||||
}
|
||||
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(command["omega"].string()));
|
||||
|
||||
this->crypto.alpha = clientAlpha;
|
||||
this->crypto.new_protocol = !use_teaspeak && ot && config::experimental_31 && (this->crypto.client_time >= 173265950 || this->crypto.client_time == 5680278000UL);
|
||||
{
|
||||
size_t beta_length = this->crypto.new_protocol ? 54 : 10;
|
||||
char beta[beta_length];
|
||||
generate_random((uint8_t *) beta, beta_length);
|
||||
this->crypto.beta = string(beta, beta_length);
|
||||
}
|
||||
|
||||
if(this->crypto.new_protocol) {
|
||||
//Pre setup
|
||||
//Generate chain
|
||||
debugMessage(this->getServerId(), "{} Got client 3.1 protocol with build timestamp {}", CLIENT_STR_LOG_PREFIX, this->crypto.client_time);
|
||||
this->crypto.chain_data = serverInstance->getTeamSpeakLicense()->license();
|
||||
this->crypto.chain_data->chain->addEphemeralEntry();
|
||||
auto rawLicense = this->crypto.chain_data->chain->exportChain();
|
||||
|
||||
//Sign license
|
||||
auto serverOmega = this->getServer()->publicServerKey();
|
||||
auto rawServerOmega = base64::decode(serverOmega);
|
||||
|
||||
auto licenseHash = digest::sha256(rawLicense);
|
||||
size_t signBufferLength = 128;
|
||||
char signBuffer[signBufferLength];
|
||||
prng_state prngState{};
|
||||
memset(&prngState, 0, sizeof(prngState));
|
||||
if(ecc_sign_hash((u_char*) licenseHash.data(), licenseHash.length(), (u_char*) signBuffer, &signBufferLength, &prngState, find_prng("sprng"), this->getServer()->serverKey()) != CRYPT_OK) {
|
||||
logError(this->getServerId(), "Failed to sign crypto chain!");
|
||||
return ts::command_result{error::vs_critical};
|
||||
}
|
||||
auto proof = base64::encode(signBuffer, signBufferLength);
|
||||
|
||||
Command initivexpand2("initivexpand2");
|
||||
initivexpand2["time"] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
initivexpand2["l"] = base64::encode(rawLicense);
|
||||
initivexpand2["beta"] = base64::encode(this->crypto.beta);
|
||||
initivexpand2["omega"] = serverOmega;
|
||||
initivexpand2["proof"] = proof;
|
||||
initivexpand2["tvd"] = "";
|
||||
initivexpand2["root"] = base64::encode((char*) this->crypto.chain_data->public_key, 32);
|
||||
initivexpand2["ot"] = 1;
|
||||
|
||||
this->sendCommand(initivexpand2);
|
||||
this->handshake.state = HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */
|
||||
} else {
|
||||
debugMessage(this->getServerId(), "{} Got non client 3.1 protocol with build timestamp {}", CLIENT_STR_LOG_PREFIX, this->crypto.client_time);
|
||||
|
||||
auto serverOmega = this->getServer()->publicServerKey();
|
||||
if(serverOmega.empty()) {
|
||||
logError(this->getServerId(), "Failed to export server public key!");
|
||||
return ts::command_result{error::vs_critical};;
|
||||
}
|
||||
|
||||
{
|
||||
Command initivexpand("initivexpand");
|
||||
initivexpand["alpha"] = base64::encode(clientAlpha);
|
||||
initivexpand["beta"] = base64::encode(this->crypto.beta);
|
||||
initivexpand["omega"] = serverOmega;
|
||||
if(use_teaspeak) {
|
||||
initivexpand["teaspeak"] = 1;
|
||||
this->handshake.state = HandshakeState::BEGIN; /* we need to start the handshake */
|
||||
} else {
|
||||
this->handshake.state = HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */
|
||||
}
|
||||
this->sendCommand0(initivexpand.build()); //If we setup the encryption now
|
||||
this->connection->encrypt_write_queue();
|
||||
}
|
||||
|
||||
{
|
||||
string error;
|
||||
if(!this->connection->getCryptHandler()->setupSharedSecret(this->crypto.alpha, this->crypto.beta, this->crypto.remote_key.get(), this->server->serverKey(), error)){
|
||||
logError(this->server->getServerId(), "Could not setup shared secret! (" + error + ")");
|
||||
return ts::command_result{error::vs_critical};
|
||||
}
|
||||
this->crypto.protocol_encrypted = true;
|
||||
}
|
||||
}
|
||||
return ts::command_result{error::ok};
|
||||
}
|
||||
|
||||
ts::command_result VoiceClient::handleCommandClientEk(Command& cmd) {
|
||||
this->last_packet_handshake = system_clock::now();
|
||||
debugMessage(this->getServerId(), "{} Got client ek!", CLIENT_STR_LOG_PREFIX);
|
||||
|
||||
auto client_key = base64::decode(cmd["ek"]);
|
||||
auto private_key = this->crypto.chain_data->chain->generatePrivateKey(this->crypto.chain_data->root_key, this->crypto.chain_data->root_index);
|
||||
|
||||
this->connection->getCryptHandler()->setupSharedSecretNew(this->crypto.alpha, this->crypto.beta, (char*) private_key.data(), client_key.data());
|
||||
this->connection->acknowledge_handler.reset();
|
||||
this->crypto.protocol_encrypted = true;
|
||||
this->sendAcknowledge(1); //Send the encrypted acknowledge (most the times the second packet; If not we're going into the resend loop)
|
||||
return ts::command_result{error::ok};
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
#include <tommath.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <algorithm>
|
||||
#include <log/LogUtils.h>
|
||||
#include "../web/WebClient.h"
|
||||
#include "VoiceClient.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts::server;
|
||||
using namespace ts::protocol;
|
||||
|
||||
//#define PKT_LOG_PING
|
||||
/* should never happen! */
|
||||
void VoiceClient::handlePacketInit(const ts::protocol::ClientPacketParser &) {}
|
||||
|
||||
//TODO Packet handlers -> move back to voice client?
|
||||
void VoiceClient::handlePacketCommand(const pipes::buffer_view& command_string) {
|
||||
std::unique_ptr<Command> command;
|
||||
command_result result{};
|
||||
try {
|
||||
command = make_unique<Command>(Command::parse(command_string, true, !ts::config::server::strict_ut8_mode));
|
||||
} catch(std::invalid_argument& ex) {
|
||||
result.reset(command_result{error::parameter_convert, std::string{ex.what()}});
|
||||
goto handle_error;
|
||||
} catch(std::exception& ex) {
|
||||
result.reset(command_result{error::parameter_convert, std::string{ex.what()}});
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
if(command->command() == "clientek") {
|
||||
result.reset(this->handleCommandClientEk(*command));
|
||||
if(result.has_error()) goto handle_error;
|
||||
} else if(command->command() == "clientinitiv") {
|
||||
result.reset(this->handleCommandClientInitIv(*command));
|
||||
if(result.has_error()) goto handle_error;
|
||||
} else this->handleCommandFull(*command, true);
|
||||
|
||||
return;
|
||||
handle_error:
|
||||
this->notifyError(result);
|
||||
result.release_data();
|
||||
}
|
||||
|
||||
void VoiceClient::handlePacketPing(const protocol::ClientPacketParser& packet) {
|
||||
if (packet.type() == protocol::PONG) {
|
||||
if(packet.payload_length() < 2) return;
|
||||
|
||||
uint16_t id = be2le16((char*) packet.payload().data_ptr());
|
||||
if (this->lastPingId == id) {
|
||||
#ifdef PKT_LOG_PING
|
||||
logMessage(this->getServerId(), "{}[Ping] Got a valid pong for ping {}. Required time: {}", CLIENT_STR_LOG_PREFIX, id, duration_cast<microseconds>(system_clock::now() - this->lastPingRequest).count() / 1000.f);
|
||||
#endif
|
||||
this->lastPingResponse = system_clock::now();
|
||||
this->ping = std::chrono::duration_cast<std::chrono::milliseconds>(this->lastPingResponse - this->lastPingRequest);
|
||||
}
|
||||
#ifdef PKT_LOG_PING
|
||||
else {
|
||||
logMessage(this->getServerId(), "{}[Ping] Got invalid pong. (Responded pong id {} but expected {})", CLIENT_STR_LOG_PREFIX, packet->packetId(), this->lastPingId);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef PKT_LOG_PING
|
||||
logMessage(this->getServerId(), "{}[Ping] Sending pong for client requested ping {}", CLIENT_STR_LOG_PREFIX, packet->packetId());
|
||||
#endif
|
||||
char buffer[2];
|
||||
le2be16(packet.packet_id(), buffer);
|
||||
this->connection->send_packet(PacketType::PONG, PacketFlag::Unencrypted, buffer, 2);
|
||||
}
|
||||
|
||||
void VoiceClient::handlePacketVoice(const protocol::ClientPacketParser& packet) {
|
||||
SpeakingClient::handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0);
|
||||
}
|
||||
|
||||
void VoiceClient::handlePacketVoiceWhisper(const ts::protocol::ClientPacketParser &packet) {
|
||||
SpeakingClient::handlePacketVoiceWhisper(packet.payload(), (packet.flags() & PacketFlag::NewProtocol) > 0);
|
||||
}
|
||||
|
||||
void VoiceClient::handlePacketAck(const protocol::ClientPacketParser& packet) {
|
||||
if(packet.payload_length() < 2) return;
|
||||
uint16_t target_id{be2le16(packet.payload().data_ptr<char>())};
|
||||
|
||||
this->connection->packet_statistics().received_acknowledge((protocol::PacketType) packet.type(), target_id | (packet.estimated_generation() << 16U));
|
||||
|
||||
string error{};
|
||||
if(!this->connection->acknowledge_handler.process_acknowledge(packet.type(), target_id, error))
|
||||
debugMessage(this->getServerId(), "{} Failed to handle acknowledge: {}", CLIENT_STR_LOG_PREFIX, error);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#include <Definitions.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "../../InstanceHandler.h"
|
||||
#include "VoiceClient.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ts::server;
|
||||
using namespace ts::protocol;
|
||||
|
||||
void VoiceClient::sendPingRequest() {
|
||||
this->lastPingRequest = std::chrono::system_clock::now();
|
||||
|
||||
auto packet = protocol::allocate_outgoing_packet(0);
|
||||
packet->ref(); /* extra ref for ourself */
|
||||
|
||||
packet->type_and_flags = (uint8_t) PacketType::PING | (uint8_t) PacketFlag::Unencrypted;
|
||||
this->connection->send_packet(packet);
|
||||
|
||||
this->lastPingId = packet->packet_id();
|
||||
packet->unref();
|
||||
|
||||
#ifdef PKT_LOG_PING
|
||||
logMessage(this->getServerId(), "{}[Ping] Sending a ping request with it {}", CLIENT_STR_LOG_PREFIX, this->lastPingId);
|
||||
#endif
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <misc/endianness.h>
|
||||
#include <dlfcn.h>
|
||||
#include "WebClient.h"
|
||||
#include "VoiceBridge.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ts;
|
||||
@@ -145,7 +144,7 @@ std::string VoiceBridge::generate_answer() {
|
||||
}
|
||||
|
||||
void VoiceBridge::execute_tick() {
|
||||
if(!this->_voice_channel) {
|
||||
if(!this->voice_channel_) {
|
||||
if(this->offer_timestamp.time_since_epoch().count() > 0 && this->offer_timestamp + chrono::seconds{20} < chrono::system_clock::now()) {
|
||||
this->offer_timestamp = chrono::system_clock::time_point();
|
||||
this->connection->callback_setup_fail(rtc::PeerConnection::ConnectionComponent::BASE, "setup timeout");
|
||||
@@ -162,10 +161,10 @@ void VoiceBridge::handle_media_stream(const std::shared_ptr<rtc::Channel> &undef
|
||||
} else if(undefined_stream->type() == rtc::CHANTYPE_AUDIO) {
|
||||
auto stream = dynamic_pointer_cast<rtc::AudioChannel>(undefined_stream);
|
||||
if(!stream) return;
|
||||
this->_audio_channel = stream;
|
||||
|
||||
logTrace(this->server_id(), "Audio channel extensions:");
|
||||
for(const auto& ex : stream->list_extensions()) {
|
||||
debugMessage(0, "{} | {}", ex->name, ex->id);
|
||||
logTrace(this->server_id(), " - {}: {}", ex->id, ex->name);
|
||||
}
|
||||
|
||||
stream->register_local_extension("urn:ietf:params:rtp-hdrext:ssrc-audio-level");
|
||||
@@ -175,58 +174,130 @@ void VoiceBridge::handle_media_stream(const std::shared_ptr<rtc::Channel> &undef
|
||||
break;
|
||||
}
|
||||
}
|
||||
stream->incoming_data_handler = [&](const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) { this->handle_audio_data(channel, data, payload_offset); };
|
||||
|
||||
if(!this->incoming_voice_channel_.lock()) {
|
||||
debugMessage(this->server_id(), "Having client's voice audio stream.");
|
||||
this->incoming_voice_channel_ = stream;
|
||||
stream->incoming_data_handler = [&](const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) {
|
||||
this->handle_audio_voice_data(channel, data, payload_offset); };
|
||||
} else if(!this->incoming_whisper_channel_.lock()) {
|
||||
debugMessage(this->server_id(), "Having client's whispers audio stream.");
|
||||
this->incoming_whisper_channel_ = stream;
|
||||
stream->incoming_data_handler = [&](const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) {
|
||||
this->handle_audio_voice_whisper_data(channel, data, payload_offset); };
|
||||
} else {
|
||||
debugMessage(this->server_id(), "Client sdp offer contains more than two voice channels.");
|
||||
}
|
||||
} else {
|
||||
logError(this->server_id(), "Got offer for unknown channel of type {}", undefined_stream->type());
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceBridge::handle_data_channel(const std::shared_ptr<rtc::DataChannel> &channel) {
|
||||
if(channel->lable() == "main") {
|
||||
this->_voice_channel = channel;
|
||||
if(channel->lable() == "main" || channel->lable() == "voice") {
|
||||
this->voice_channel_ = channel;
|
||||
debugMessage(this->server_id(), "{} Got voice channel!", CLIENT_STR_LOG_PREFIX_(this->owner()));
|
||||
this->callback_initialized();
|
||||
|
||||
weak_ptr<rtc::DataChannel> weak_channel = channel;
|
||||
channel->callback_binary = [&, weak_channel](const pipes::buffer_view& buffer) {
|
||||
if(buffer.length() < 2)
|
||||
return;
|
||||
|
||||
this->callback_voice_data(buffer.view(2), buffer[0] == 1);
|
||||
};
|
||||
|
||||
channel->callback_close = [&, weak_channel] {
|
||||
auto channel_ref = weak_channel.lock();
|
||||
if(channel_ref == this->voice_channel_) {
|
||||
this->voice_channel_ = nullptr;
|
||||
//TODO may callback?
|
||||
debugMessage(this->server_id(), "{} Voice channel disconnected!", CLIENT_STR_LOG_PREFIX_(this->owner()));
|
||||
}
|
||||
};
|
||||
} else if(channel->lable() == "voice-whisper") {
|
||||
this->voice_whisper_channel_ = channel;
|
||||
debugMessage(this->server_id(), "{} Got voice whisper channel", CLIENT_STR_LOG_PREFIX_(this->owner()));
|
||||
|
||||
weak_ptr<rtc::DataChannel> weak_channel = channel;
|
||||
channel->callback_binary = [&, weak_channel](const pipes::buffer_view& buffer) {
|
||||
if(buffer.length() < 1)
|
||||
return;
|
||||
|
||||
this->callback_voice_whisper_data(buffer.view(1), buffer[0] == 1);
|
||||
};
|
||||
|
||||
channel->callback_close = [&, weak_channel] {
|
||||
auto channel_ref = weak_channel.lock();
|
||||
if(channel_ref == this->voice_whisper_channel_) {
|
||||
this->voice_whisper_channel_ = nullptr;
|
||||
debugMessage(this->server_id(), "{} Voice whisper channel has been closed.", CLIENT_STR_LOG_PREFIX_(this->owner()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
weak_ptr<rtc::DataChannel> weak_channel = channel;
|
||||
channel->callback_binary = [&, weak_channel](const pipes::buffer_view& buffer) {
|
||||
this->callback_voice_data(buffer.view(2), buffer[0] == 1, buffer[1] == 1); /* buffer.substr(2), buffer[0] == 1, buffer[1] == 1 */
|
||||
};
|
||||
|
||||
channel->callback_close = [&, channel] {
|
||||
if(channel == this->_voice_channel) {
|
||||
this->_voice_channel = nullptr;
|
||||
//TODO may callback?
|
||||
debugMessage(this->server_id(), "{} Voice channel disconnected!", CLIENT_STR_LOG_PREFIX_(this->owner()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void VoiceBridge::handle_audio_data(const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) {
|
||||
void VoiceBridge::handle_audio_voice_data(const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) {
|
||||
if(channel->codec->type != rtc::codec::Codec::OPUS) {
|
||||
debugMessage(this->server_id(), "{} Got unknown codec ({})!", CLIENT_STR_LOG_PREFIX_(this->owner()), channel->codec->type);
|
||||
//debugMessage(this->server_id(), "{} Got unknown codec ({})!", CLIENT_STR_LOG_PREFIX_(this->owner()), channel->codec->type);
|
||||
return;
|
||||
}
|
||||
|
||||
auto ac = _audio_channel.lock();
|
||||
if(!ac) return;
|
||||
this->handle_audio_voice_x_data(&this->voice_state, data, payload_offset);
|
||||
}
|
||||
|
||||
for(const auto& ext : ac->list_extensions(rtc::direction::incoming)) {
|
||||
void VoiceBridge::handle_audio_voice_whisper_data(const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) {
|
||||
if(channel->codec->type != rtc::codec::Codec::OPUS) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->handle_audio_voice_x_data(&this->whisper_state, data, payload_offset);
|
||||
}
|
||||
|
||||
void VoiceBridge::handle_audio_voice_x_data(VoiceStateData *state, const pipes::buffer_view &data, size_t payload_offset) {
|
||||
bool is_silence{false};
|
||||
|
||||
auto audio_channel = state->channel.lock();
|
||||
if(!audio_channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(const auto& ext : audio_channel->list_extensions(rtc::direction::incoming)) {
|
||||
if(ext->name == "urn:ietf:params:rtp-hdrext:ssrc-audio-level") {
|
||||
int level;
|
||||
if(rtc::protocol::rtp_header_extension_parse_audio_level(data, ext->id, &level) == 0) {
|
||||
//debugMessage(this->server_id(), "Audio level: {}", level);
|
||||
if(level == 127) return; //Silence
|
||||
if(level == 127) {
|
||||
is_silence = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
//int level;
|
||||
//rtc::protocol::rtp_header_extension_parse_audio_level((char*) data.data(), data.length(), 1, &level);
|
||||
auto target_buffer = buffer::allocate_buffer(data.length() - payload_offset + 3);
|
||||
le2be16(this->voice.packet_id++, (char*) target_buffer.data_ptr());
|
||||
target_buffer[2] = 5;
|
||||
memcpy(&target_buffer[3], &data[payload_offset], data.length() - payload_offset);
|
||||
|
||||
this->callback_voice_data(target_buffer, this->voice.packet_id < 7, false);
|
||||
if(is_silence) {
|
||||
if(state->muted) {
|
||||
/* the muted state is already set */
|
||||
return;
|
||||
}
|
||||
state->muted = true;
|
||||
|
||||
auto target_buffer = buffer::allocate_buffer(3);
|
||||
le2be16(state->sequence_packet_id++, (char*) target_buffer.data_ptr());
|
||||
target_buffer[2] = 5;
|
||||
|
||||
state->callback(target_buffer, false);
|
||||
} else {
|
||||
if(state->muted) {
|
||||
state->muted = false;
|
||||
}
|
||||
|
||||
auto target_buffer = buffer::allocate_buffer(data.length() - payload_offset + 3);
|
||||
le2be16(state->sequence_packet_id++, (char*) target_buffer.data_ptr());
|
||||
target_buffer[2] = 5;
|
||||
memcpy(&target_buffer[3], &data[payload_offset], data.length() - payload_offset);
|
||||
|
||||
state->callback(target_buffer, state->sequence_packet_id < 7);
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,14 @@ namespace ts {
|
||||
namespace web {
|
||||
class VoiceBridge {
|
||||
public:
|
||||
typedef std::function<void(const pipes::buffer_view&, bool, bool)> cb_voice_data;
|
||||
typedef std::function<void(const pipes::buffer_view&, bool /* is sequence start */)> cb_voice_data;
|
||||
typedef std::function<void(const rtc::IceCandidate&)> cb_ice_candidate;
|
||||
typedef std::function<void(const std::string& /* sdpMid */, int /* sdpMLineIndex */)> cb_ice_candidate_finish;
|
||||
typedef std::function<void()> cb_initialized;
|
||||
typedef std::function<void()> cb_failed;
|
||||
|
||||
std::shared_ptr<rtc::DataChannel> voice_channel() { return this->_voice_channel; }
|
||||
std::shared_ptr<rtc::DataChannel> voice_channel() { return this->voice_channel_; }
|
||||
std::shared_ptr<rtc::DataChannel> voice_whisper_channel() { return this->voice_whisper_channel_; }
|
||||
|
||||
explicit VoiceBridge(const std::shared_ptr<server::WebClient>&);
|
||||
virtual ~VoiceBridge();
|
||||
@@ -31,11 +32,20 @@ namespace ts {
|
||||
cb_ice_candidate callback_ice_candidate;
|
||||
cb_ice_candidate_finish callback_ice_candidate_finished;
|
||||
cb_voice_data callback_voice_data;
|
||||
cb_voice_data callback_voice_whisper_data;
|
||||
cb_initialized callback_initialized;
|
||||
cb_failed callback_failed;
|
||||
|
||||
void execute_tick();
|
||||
private:
|
||||
struct VoiceStateData {
|
||||
uint16_t sequence_packet_id{0};
|
||||
bool muted{true};
|
||||
|
||||
std::weak_ptr<rtc::AudioChannel>& channel;
|
||||
cb_voice_data& callback;
|
||||
};
|
||||
|
||||
static void callback_log(void* ptr, pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...);
|
||||
|
||||
inline int server_id();
|
||||
@@ -43,17 +53,30 @@ namespace ts {
|
||||
|
||||
void handle_media_stream(const std::shared_ptr<rtc::Channel>& /* stream */);
|
||||
void handle_data_channel(const std::shared_ptr<rtc::DataChannel> & /* channel */);
|
||||
void handle_audio_data(const std::shared_ptr<rtc::MediaChannel>& /* channel */, const pipes::buffer_view& /* buffer */, size_t /* payload offset */);
|
||||
|
||||
void handle_audio_voice_data(const std::shared_ptr<rtc::MediaChannel>& /* channel */, const pipes::buffer_view& /* buffer */, size_t /* payload offset */);
|
||||
void handle_audio_voice_whisper_data(const std::shared_ptr<rtc::MediaChannel>& /* channel */, const pipes::buffer_view& /* buffer */, size_t /* payload offset */);
|
||||
|
||||
static void handle_audio_voice_x_data(VoiceStateData* /* state */, const pipes::buffer_view& /* buffer */, size_t /* payload offset */);
|
||||
|
||||
std::weak_ptr<server::WebClient> _owner;
|
||||
std::chrono::system_clock::time_point offer_timestamp;
|
||||
std::unique_ptr<rtc::PeerConnection> connection;
|
||||
std::shared_ptr<rtc::DataChannel> _voice_channel;
|
||||
std::weak_ptr<rtc::AudioChannel> _audio_channel;
|
||||
struct {
|
||||
uint16_t packet_id = 0;
|
||||
bool muted = true;
|
||||
} voice;
|
||||
|
||||
std::shared_ptr<rtc::DataChannel> voice_channel_{};
|
||||
std::shared_ptr<rtc::DataChannel> voice_whisper_channel_{};
|
||||
|
||||
std::weak_ptr<rtc::AudioChannel> incoming_voice_channel_{};
|
||||
std::weak_ptr<rtc::AudioChannel> incoming_whisper_channel_{};
|
||||
|
||||
VoiceStateData voice_state{
|
||||
.channel = this->incoming_voice_channel_,
|
||||
.callback = this->callback_voice_data
|
||||
};
|
||||
VoiceStateData whisper_state{
|
||||
.channel = this->incoming_whisper_channel_,
|
||||
.callback = this->callback_voice_whisper_data
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ using namespace ts::protocol;
|
||||
void WebClient::handleMessageWrite(int fd, short, void *) {
|
||||
auto self_lock = _this.lock();
|
||||
|
||||
unique_lock buffer_lock(this->queue_lock);
|
||||
unique_lock buffer_lock(this->queue_mutex);
|
||||
if(this->queue_write.empty()) return;
|
||||
|
||||
auto buffer = this->queue_write[0];
|
||||
@@ -85,7 +85,7 @@ void WebClient::handleMessageRead(int fd, short, void *) {
|
||||
pbuffer.write(buffer, length);
|
||||
|
||||
{
|
||||
lock_guard lock(this->queue_lock);
|
||||
lock_guard lock(this->queue_mutex);
|
||||
this->queue_read.push_back(std::move(pbuffer));
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ void WebClient::handleMessageRead(int fd, short, void *) {
|
||||
void WebClient::enqueue_raw_packet(const pipes::buffer_view &msg) {
|
||||
auto buffer = msg.owns_buffer() ? msg.own_buffer() : msg.own_buffer(); /* TODO: Use buffer::allocate_buffer(...) */
|
||||
{
|
||||
lock_guard queue_lock(this->queue_lock);
|
||||
lock_guard queue_lock(this->queue_mutex);
|
||||
this->queue_write.push_back(buffer);
|
||||
}
|
||||
{
|
||||
@@ -124,11 +124,11 @@ inline bool is_ssl_handshake_header(const pipes::buffer_view& buffer) {
|
||||
}
|
||||
|
||||
void WebClient::processNextMessage(const std::chrono::system_clock::time_point& /* scheduled */) {
|
||||
lock_guard execute_lock(this->execute_lock);
|
||||
lock_guard execute_lock(this->execute_mutex);
|
||||
if(this->state != ConnectionState::INIT_HIGH && this->state != ConnectionState::INIT_LOW && this->state != ConnectionState::CONNECTED)
|
||||
return;
|
||||
|
||||
unique_lock buffer_lock(this->queue_lock);
|
||||
unique_lock buffer_lock(this->queue_mutex);
|
||||
if(this->queue_read.empty())
|
||||
return;
|
||||
|
||||
@@ -152,6 +152,7 @@ void WebClient::processNextMessage(const std::chrono::system_clock::time_point&
|
||||
this->ws_handler.process_incoming_data(buffer);
|
||||
}
|
||||
|
||||
if(has_next)
|
||||
if(has_next) {
|
||||
this->registerMessageProcess();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <src/client/ConnectedClient.h>
|
||||
#include <misc/std_unique_ptr.h>
|
||||
#include <src/client/SpeakingClient.h>
|
||||
#include "../../manager/ActionLogger.h"
|
||||
|
||||
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
|
||||
#define TCP_NOPUSH TCP_CORK
|
||||
@@ -28,6 +29,8 @@ WebClient::WebClient(WebControlServer* server, int fd) : SpeakingClient(server->
|
||||
assert(server->getTS());
|
||||
this->state = ConnectionState::INIT_LOW;
|
||||
this->file_descriptor = fd;
|
||||
|
||||
debugMessage(this->server->getServerId(), " Creating WebClient instance at {}", (void*) this);
|
||||
}
|
||||
|
||||
void WebClient::initialize() {
|
||||
@@ -83,8 +86,8 @@ void WebClient::initialize() {
|
||||
}
|
||||
};
|
||||
|
||||
this->ws_handler.on_connect = bind(&WebClient::onWSConnected, this);
|
||||
this->ws_handler.on_disconnect = bind(&WebClient::onWSDisconnected, this, placeholders::_1);
|
||||
this->ws_handler.on_connect = std::bind(&WebClient::onWSConnected, this);
|
||||
this->ws_handler.on_disconnect = std::bind(&WebClient::onWSDisconnected, this, placeholders::_1);
|
||||
|
||||
this->ws_handler.callback_data([&](const pipes::WSMessage& msg){
|
||||
this->onWSMessage(msg);
|
||||
@@ -120,6 +123,7 @@ void WebClient::initialize() {
|
||||
|
||||
WebClient::~WebClient() {
|
||||
memtrack::freed<WebClient>(this);
|
||||
debugMessage(this->server->getServerId(), " Destroying WebClient instance at {}", (void*) this);
|
||||
}
|
||||
|
||||
static Json::StreamWriterBuilder stream_write_builder = []{
|
||||
@@ -152,22 +156,29 @@ void WebClient::sendJson(const Json::Value& json) {
|
||||
}
|
||||
|
||||
void WebClient::sendCommand(const ts::Command &command, bool low) {
|
||||
Json::Value value = command.buildJson();
|
||||
value["type"] = "command";
|
||||
this->sendJson(value);
|
||||
if(this->allow_raw_commands) {
|
||||
Json::Value value{};
|
||||
value["type"] = "command-raw";
|
||||
value["payload"] = command.build();
|
||||
this->sendJson(value);
|
||||
} else {
|
||||
Json::Value value = command.buildJson();
|
||||
value["type"] = "command";
|
||||
this->sendJson(value);
|
||||
}
|
||||
}
|
||||
|
||||
void WebClient::sendCommand(const ts::command_builder &command, bool low) {
|
||||
#if false
|
||||
Json::Value value{};
|
||||
value["type"] = "command2";
|
||||
value["payload"] = command.build();
|
||||
this->sendJson(value);
|
||||
#else
|
||||
auto data = command.build();
|
||||
Command parsed_command = Command::parse(pipes::buffer_view{data.data(), data.length()}, true, false);
|
||||
this->sendCommand(parsed_command, low);
|
||||
#endif
|
||||
if(this->allow_raw_commands) {
|
||||
Json::Value value{};
|
||||
value["type"] = "command-raw";
|
||||
value["payload"] = command.build();
|
||||
this->sendJson(value);
|
||||
} else {
|
||||
auto data = command.build();
|
||||
Command parsed_command = Command::parse(pipes::buffer_view{data.data(), data.length()}, true, false);
|
||||
this->sendCommand(parsed_command, low);
|
||||
}
|
||||
}
|
||||
|
||||
bool WebClient::close_connection(const std::chrono::system_clock::time_point& timeout) {
|
||||
@@ -209,7 +220,7 @@ bool WebClient::close_connection(const std::chrono::system_clock::time_point& ti
|
||||
flag_flushed = true;
|
||||
|
||||
{
|
||||
lock_guard lock(self_lock->queue_lock);
|
||||
lock_guard lock(self_lock->queue_mutex);
|
||||
flag_flushed &= self_lock->queue_read.empty();
|
||||
flag_flushed &= self_lock->queue_write.empty();
|
||||
}
|
||||
@@ -254,6 +265,11 @@ command_result WebClient::handleCommand(Command &command) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if(command.command() == "setwhispertarget") {
|
||||
return this->handleCommandSetWhisperTarget(command);
|
||||
} else if(command.command() == "clearwhispertarget") {
|
||||
return this->handleCommandClearWhisperTarget(command);
|
||||
}
|
||||
return SpeakingClient::handleCommand(command);
|
||||
}
|
||||
|
||||
@@ -322,7 +338,7 @@ void WebClient::onWSDisconnected(const string& error) {
|
||||
|
||||
void WebClient::onWSMessage(const pipes::WSMessage &message) {
|
||||
if(message.code == pipes::OpCode::TEXT)
|
||||
this->handleMessage(message.data.string());
|
||||
this->handleMessage(message.data);
|
||||
else if(message.code == pipes::OpCode::PING) {
|
||||
logTrace(this->getServerId(), "{} Received ping on web socket. Application data length: {}. Sending pong", CLIENT_STR_LOG_PREFIX, message.data.length());
|
||||
this->ws_handler.send({pipes::PONG, message.data});
|
||||
@@ -363,7 +379,7 @@ void WebClient::disconnectFinal() {
|
||||
auto self_lock = this->_this.lock();
|
||||
{
|
||||
/* waiting to finish all executes */
|
||||
lock_guard lock(this->execute_lock);
|
||||
lock_guard lock(this->execute_mutex);
|
||||
}
|
||||
|
||||
if(this->flush_thread.get_id() == this_thread::get_id())
|
||||
@@ -414,27 +430,30 @@ void WebClient::disconnectFinal() {
|
||||
this->handle->unregisterConnection(static_pointer_cast<WebClient>(self_lock));
|
||||
}
|
||||
|
||||
Json::CharReaderBuilder json_reader_builder = []{
|
||||
Json::CharReaderBuilder json_reader_builder = []() noexcept {
|
||||
Json::CharReaderBuilder reader_builder;
|
||||
|
||||
return reader_builder;
|
||||
}();
|
||||
|
||||
void WebClient::handleMessage(const std::string &message) {
|
||||
void WebClient::handleMessage(const pipes::buffer_view &message) {
|
||||
/* Not really a need, this will directly be called via the ssl ws pipe, which has been triggered via progress message */
|
||||
threads::MutexLock lock(this->execute_lock);
|
||||
threads::MutexLock lock(this->execute_mutex);
|
||||
Json::Value val;
|
||||
try {
|
||||
unique_ptr<Json::CharReader> reader{json_reader_builder.newCharReader()};
|
||||
|
||||
string error_message;
|
||||
if(!reader->parse(message.data(),message.data() + message.length(), &val, &error_message)) throw Json::Exception("Could not parse payload! (" + error_message + ")");
|
||||
if(!reader->parse(message.data_ptr<char>(),message.data_ptr<char>() + message.length(), &val, &error_message)) {
|
||||
throw Json::Exception("Could not parse payload! (" + error_message + ")");
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
logError(this->server->getServerId(), "Could not parse web message! Message: " + string(ex.what()));
|
||||
logTrace(this->server->getServerId(), "Payload: " + message);
|
||||
logError(this->server->getServerId(), "Could not parse web message! Message: {}", std::string{ex.what()});
|
||||
logTrace(this->server->getServerId(), "Payload: {}", message.string());
|
||||
return;
|
||||
}
|
||||
logTrace(this->server->getServerId(), "[{}] Read message {}", CLIENT_STR_LOG_PREFIX_(this), message);
|
||||
logTrace(this->server->getServerId(), "[{}] Read message {}", CLIENT_STR_LOG_PREFIX_(this), std::string_view{message.data_ptr<char>(), message.length()});
|
||||
|
||||
try {
|
||||
if(val["type"].isNull()) {
|
||||
logError(this->server->getServerId(), "[{}] Invalid web json package!");
|
||||
@@ -454,7 +473,7 @@ void WebClient::handleMessage(const std::string &message) {
|
||||
} else if(val["type"].asString() == "WebRTC") {
|
||||
auto subType = val["request"].asString();
|
||||
if(subType == "create") {
|
||||
unique_lock voice_bridge_lock(this->voice_bridge_lock);
|
||||
std::unique_lock voice_bridge_lock_{this->voice_bridge_lock};
|
||||
if(this->voice_bridge) {
|
||||
logError(this->server->getServerId(), "[{}] Tried to register a WebRTC channel twice!", CLIENT_STR_LOG_PREFIX_(this));
|
||||
|
||||
@@ -463,13 +482,43 @@ void WebClient::handleMessage(const std::string &message) {
|
||||
lock = nullptr;
|
||||
}).detach();
|
||||
}
|
||||
//TODO test if bridge already exists!
|
||||
this->voice_bridge = make_unique<web::VoiceBridge>(dynamic_pointer_cast<WebClient>(_this.lock())); //FIXME Add config
|
||||
|
||||
this->voice_bridge->callback_voice_data = [&](const pipes::buffer_view& buffer, bool a, bool b) {
|
||||
this->voice_bridge = make_unique<web::VoiceBridge>(dynamic_pointer_cast<WebClient>(this->ref())); //FIXME Add config
|
||||
|
||||
this->voice_bridge->callback_voice_data = [&](const pipes::buffer_view& buffer, bool head) {
|
||||
/* may somehow get the "real" packet size? */
|
||||
this->connectionStatistics->logIncomingPacket(stats::ConnectionStatistics::category::VOICE, buffer.length());
|
||||
this->handlePacketVoice(buffer, a, b);
|
||||
this->handlePacketVoice(buffer, head, false);
|
||||
};
|
||||
this->voice_bridge->callback_voice_whisper_data = [&](const pipes::buffer_view& buffer, bool head) {
|
||||
/* may somehow get the "real" packet size? */
|
||||
this->connectionStatistics->logIncomingPacket(stats::ConnectionStatistics::category::VOICE, buffer.length());
|
||||
|
||||
constexpr static auto kTempBufferSize{2048};
|
||||
char temp_buffer[kTempBufferSize];
|
||||
size_t offset{0};
|
||||
|
||||
/* copy the voice header */
|
||||
memcpy(temp_buffer, buffer.data_ptr(), 3);
|
||||
offset += 3;
|
||||
|
||||
bool is_new;
|
||||
{
|
||||
std::lock_guard whisper_header_lock{this->whisper.mutex};
|
||||
if(!this->whisper.is_set) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(temp_buffer + offset, this->whisper.target_header.data_ptr(), this->whisper.target_header.length());
|
||||
offset += this->whisper.target_header.length();
|
||||
|
||||
is_new = this->whisper.is_new_header;
|
||||
}
|
||||
|
||||
memcpy(temp_buffer + offset, buffer.data_ptr<char>() + 3, buffer.length() - 3);
|
||||
offset += buffer.length() - 3;
|
||||
|
||||
this->handlePacketVoiceWhisper(pipes::buffer_view{temp_buffer, offset}, is_new, head);
|
||||
};
|
||||
this->voice_bridge->callback_initialized = [&](){
|
||||
debugMessage(this->getServerId(), "{} Voice bridge initialized!", CLIENT_STR_LOG_PREFIX);
|
||||
@@ -517,7 +566,7 @@ void WebClient::handleMessage(const std::string &message) {
|
||||
};
|
||||
|
||||
auto vbp = &*this->voice_bridge;
|
||||
voice_bridge_lock.unlock();
|
||||
voice_bridge_lock_.unlock();
|
||||
shared_lock read_voice_bridge_lock(this->voice_bridge_lock);
|
||||
|
||||
if(vbp != &*this->voice_bridge) {
|
||||
@@ -624,6 +673,8 @@ void WebClient::handleMessage(const std::string &message) {
|
||||
}
|
||||
this->js_ping.last_response = system_clock::now();
|
||||
this->js_ping.value = duration_cast<nanoseconds>(this->js_ping.last_response - this->js_ping.last_request);
|
||||
} else if(val["type"].asString() == "enable-raw-commands") {
|
||||
this->allow_raw_commands = true;
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
logError(this->server->getServerId(), "Could not handle json packet! Message {}", ex.what());
|
||||
@@ -634,6 +685,10 @@ void WebClient::handleMessage(const std::string &message) {
|
||||
//candidate:0 1 UDP 2122252543 192.168.43.141 34590 typ host
|
||||
|
||||
bool WebClient::disconnect(const std::string &reason) {
|
||||
auto old_channel = this->currentChannel;
|
||||
if(old_channel) {
|
||||
serverInstance->action_logger()->client_channel_logger.log_client_leave(this->getServerId(), this->ref(), old_channel->channelId(), old_channel->name());
|
||||
}
|
||||
{
|
||||
unique_lock server_channel_lock(this->server->channel_tree_lock);
|
||||
{
|
||||
@@ -656,28 +711,134 @@ command_result WebClient::handleCommandClientInit(Command &command) {
|
||||
bool WebClient::shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender) {
|
||||
shared_lock read_voice_bridge_lock(this->voice_bridge_lock);
|
||||
if(!this->voice_bridge || !this->voice_bridge->voice_channel()) return false;
|
||||
|
||||
return SpeakingClient::shouldReceiveVoice(sender);
|
||||
}
|
||||
|
||||
void WebClient::handlePacketVoiceWhisper(const pipes::buffer_view &string, bool flag) {
|
||||
shared_lock read_voice_bridge_lock(this->voice_bridge_lock);
|
||||
if(!this->voice_bridge || !this->voice_bridge->voice_channel()) return;
|
||||
SpeakingClient::handlePacketVoiceWhisper(string, flag);
|
||||
}
|
||||
|
||||
void WebClient::send_voice_packet(const pipes::buffer_view &view, const SpeakingClient::VoicePacketFlags &flags) {
|
||||
shared_lock read_voice_bridge_lock(this->voice_bridge_lock);
|
||||
std::shared_lock read_voice_bridge_lock(this->voice_bridge_lock);
|
||||
if(this->voice_bridge) {
|
||||
auto channel = this->voice_bridge->voice_channel();
|
||||
if(channel) {
|
||||
channel->send(view);
|
||||
read_voice_bridge_lock.unlock();
|
||||
|
||||
/* may somehow get the "real" packet size? */
|
||||
this->connectionStatistics->logOutgoingPacket(stats::ConnectionStatistics::category::VOICE, view.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebClient::send_voice_whisper_packet(const pipes::buffer_view &view, const SpeakingClient::VoicePacketFlags &flags) {
|
||||
logError(this->server->getServerId(), "Web client got whisper packet");
|
||||
//As well log the data!
|
||||
void WebClient::send_voice_whisper_packet(const pipes::buffer_view &, const pipes::buffer_view &teaspeak_packet, const SpeakingClient::VoicePacketFlags &flags) {
|
||||
std::shared_lock read_voice_bridge_lock{this->voice_bridge_lock};
|
||||
if(this->voice_bridge) {
|
||||
auto channel = this->voice_bridge->voice_whisper_channel();
|
||||
if(channel) {
|
||||
uint8_t buffer[teaspeak_packet.length() + 1];
|
||||
memcpy(buffer + 1, teaspeak_packet.data_ptr(), teaspeak_packet.length());
|
||||
buffer[0] = 0;
|
||||
if(flags.head) {
|
||||
buffer[0] |= 0x1U;
|
||||
}
|
||||
channel->send(pipes::buffer{buffer, teaspeak_packet.length() + 1});
|
||||
read_voice_bridge_lock.unlock();
|
||||
|
||||
/* may somehow get the "real" packet size? */
|
||||
this->connectionStatistics->logOutgoingPacket(stats::ConnectionStatistics::category::VOICE, teaspeak_packet.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command_result WebClient::handleCommandSetWhisperTarget(Command &command) {
|
||||
auto server = this->getServer();
|
||||
if(!server) {
|
||||
return command_result{error::server_unbound};
|
||||
}
|
||||
|
||||
if(command.hasParm("new")) {
|
||||
auto type = command["type"].as<uint8_t>();
|
||||
auto target = command["target"].as<uint8_t>();
|
||||
auto target_id = command["id"].as<uint64_t>();
|
||||
|
||||
std::lock_guard whisper_buffer_lock{this->whisper.mutex};
|
||||
this->whisper.is_set = true;
|
||||
this->whisper.is_new_header = true;
|
||||
this->whisper.target_header.resize(10);
|
||||
|
||||
this->whisper.target_header[0] = type;
|
||||
this->whisper.target_header[1] = target;
|
||||
le2be64(target_id, &this->whisper.target_header[2]);
|
||||
|
||||
return command_result{error::ok};
|
||||
} else {
|
||||
if(command.bulkCount() > 255) {
|
||||
return command_result{error::parameter_invalid_count};
|
||||
}
|
||||
|
||||
std::vector<ClientId> client_ids{};
|
||||
std::vector<ChannelId> channel_ids{};
|
||||
|
||||
client_ids.reserve(command.bulkCount());
|
||||
channel_ids.reserve(command.bulkCount());
|
||||
|
||||
std::optional<decltype(server->getClients())> server_clients{};
|
||||
|
||||
for(size_t bulk{0}; bulk < command.bulkCount(); bulk++) {
|
||||
if(command[bulk].has("cid")) {
|
||||
channel_ids.push_back(command[bulk]["cid"]);
|
||||
}
|
||||
|
||||
if(command[bulk].has("clid")) {
|
||||
channel_ids.push_back(command[bulk]["clid"]);
|
||||
}
|
||||
|
||||
if(command[bulk].has("cluid")) {
|
||||
auto client_unique_id = command[bulk]["cluid"].string();
|
||||
if(!server_clients.has_value()) {
|
||||
server_clients = server->getClients();
|
||||
}
|
||||
|
||||
for(const auto& client : *server_clients) {
|
||||
if(client->getUid() == client_unique_id) {
|
||||
client_ids.push_back(client->getClientId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check if we're exceeding the protocol limit */
|
||||
if(client_ids.size() > 255) {
|
||||
return command_result{error::whisper_too_many_targets};
|
||||
}
|
||||
if(channel_ids.size() > 255) {
|
||||
return command_result{error::whisper_too_many_targets};
|
||||
}
|
||||
|
||||
/* generate the whisper target header */
|
||||
std::lock_guard whisper_buffer_lock{this->whisper.mutex};
|
||||
this->whisper.is_set = true;
|
||||
this->whisper.is_new_header = false;
|
||||
this->whisper.target_header.resize(client_ids.size() * 2 + channel_ids.size() * 8 + 2);
|
||||
static_assert(sizeof(ChannelId) == 8);
|
||||
static_assert(sizeof(ClientId) == 2);
|
||||
|
||||
size_t offset{0};
|
||||
|
||||
this->whisper.target_header[0] = channel_ids.size();
|
||||
this->whisper.target_header[1] = client_ids.size();
|
||||
offset += 2;
|
||||
|
||||
memcpy(this->whisper.target_header.data_ptr<char>() + offset, channel_ids.data(), channel_ids.size() * 8);
|
||||
offset += channel_ids.size() * 8;
|
||||
|
||||
memcpy(this->whisper.target_header.data_ptr<char>() + offset, client_ids.data(), client_ids.size() * 2);
|
||||
//offset += channel_ids.size() * 2;
|
||||
}
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
command_result WebClient::handleCommandClearWhisperTarget(Command &command) {
|
||||
std::lock_guard whisper_buffer_lock{this->whisper.mutex};
|
||||
this->whisper.is_set = false;
|
||||
return command_result{error::ok};
|
||||
}
|
||||
@@ -14,105 +14,113 @@
|
||||
#include <json/json.h>
|
||||
#include <EventLoop.h>
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class WebControlServer;
|
||||
namespace ts::server {
|
||||
class WebControlServer;
|
||||
|
||||
class WebClient : public SpeakingClient {
|
||||
friend class WebControlServer;
|
||||
public:
|
||||
WebClient(WebControlServer*, int socketFd);
|
||||
~WebClient() override;
|
||||
class WebClient : public SpeakingClient {
|
||||
friend class WebControlServer;
|
||||
public:
|
||||
WebClient(WebControlServer*, int socketFd);
|
||||
~WebClient() override;
|
||||
|
||||
void sendJson(const Json::Value&);
|
||||
void sendCommand(const ts::Command &command, bool low = false) override;
|
||||
void sendCommand(const ts::command_builder &command, bool low) override;
|
||||
void sendJson(const Json::Value&);
|
||||
void sendCommand(const ts::Command &command, bool low) override;
|
||||
void sendCommand(const ts::command_builder &command, bool low) override;
|
||||
|
||||
bool disconnect(const std::string &reason) override;
|
||||
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
|
||||
bool disconnect(const std::string &reason) override;
|
||||
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
|
||||
|
||||
bool shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender) override;
|
||||
bool shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender) override;
|
||||
|
||||
inline std::chrono::nanoseconds client_ping() { return this->client_ping_layer_7(); }
|
||||
inline std::chrono::nanoseconds client_ping_layer_5() { return this->ping.value; }
|
||||
inline std::chrono::nanoseconds client_ping_layer_7() { return this->js_ping.value; }
|
||||
protected:
|
||||
void handlePacketVoiceWhisper(const pipes::buffer_view &string, bool) override;
|
||||
|
||||
protected:
|
||||
void tick(const std::chrono::system_clock::time_point&) override; /* Every 500ms */
|
||||
[[nodiscard]] inline std::chrono::nanoseconds client_ping() const { return this->client_ping_layer_7(); }
|
||||
[[nodiscard]] inline std::chrono::nanoseconds client_ping_layer_5() const { return this->ping.value; }
|
||||
[[nodiscard]] inline std::chrono::nanoseconds client_ping_layer_7() const { return this->js_ping.value; }
|
||||
|
||||
void applySelfLock(const std::shared_ptr<WebClient> &cl){ _this = cl; }
|
||||
private:
|
||||
WebControlServer* handle;
|
||||
std::chrono::time_point<std::chrono::system_clock> connectedTimestamp;
|
||||
protected:
|
||||
void tick(const std::chrono::system_clock::time_point&) override; /* Every 500ms */
|
||||
|
||||
std::shared_mutex voice_bridge_lock;
|
||||
std::unique_ptr<web::VoiceBridge> voice_bridge;
|
||||
int file_descriptor;
|
||||
void applySelfLock(const std::shared_ptr<WebClient> &cl){ _this = cl; }
|
||||
private:
|
||||
WebControlServer* handle;
|
||||
|
||||
std::mutex event_lock;
|
||||
::event* readEvent;
|
||||
::event* writeEvent;
|
||||
std::shared_mutex voice_bridge_lock;
|
||||
std::unique_ptr<web::VoiceBridge> voice_bridge;
|
||||
int file_descriptor;
|
||||
|
||||
struct {
|
||||
uint8_t current_id = 0;
|
||||
std::chrono::system_clock::time_point last_request;
|
||||
std::chrono::system_clock::time_point last_response;
|
||||
bool allow_raw_commands{false};
|
||||
bool ssl_detected{false};
|
||||
bool ssl_encrypted{true};
|
||||
pipes::SSL ssl_handler;
|
||||
pipes::WebSocket ws_handler;
|
||||
|
||||
std::chrono::nanoseconds value{};
|
||||
std::chrono::nanoseconds timeout{2000};
|
||||
} ping;
|
||||
std::mutex event_lock;
|
||||
::event* readEvent;
|
||||
::event* writeEvent;
|
||||
|
||||
struct {
|
||||
uint8_t current_id = 0;
|
||||
std::chrono::system_clock::time_point last_request;
|
||||
std::chrono::system_clock::time_point last_response;
|
||||
struct {
|
||||
uint8_t current_id{0};
|
||||
std::chrono::system_clock::time_point last_request;
|
||||
std::chrono::system_clock::time_point last_response;
|
||||
|
||||
std::chrono::nanoseconds value{};
|
||||
std::chrono::nanoseconds timeout{2000};
|
||||
} js_ping;
|
||||
std::chrono::nanoseconds value{};
|
||||
std::chrono::nanoseconds timeout{2000};
|
||||
} ping;
|
||||
|
||||
std::mutex queue_lock;
|
||||
std::deque<pipes::buffer> queue_read;
|
||||
std::deque<pipes::buffer> queue_write;
|
||||
threads::Mutex execute_lock; /* needs to be recursive! */
|
||||
struct {
|
||||
uint8_t current_id{0};
|
||||
std::chrono::system_clock::time_point last_request;
|
||||
std::chrono::system_clock::time_point last_response;
|
||||
|
||||
std::thread flush_thread;
|
||||
std::recursive_mutex close_lock;
|
||||
private:
|
||||
void initialize();
|
||||
std::chrono::nanoseconds value{};
|
||||
std::chrono::nanoseconds timeout{2000};
|
||||
} js_ping;
|
||||
|
||||
bool ssl_detected = false;
|
||||
bool ssl_encrypted = true;
|
||||
pipes::SSL ssl_handler;
|
||||
pipes::WebSocket ws_handler;
|
||||
void handleMessageRead(int, short, void*);
|
||||
void handleMessageWrite(int, short, void*);
|
||||
void enqueue_raw_packet(const pipes::buffer_view& /* buffer */);
|
||||
std::mutex queue_mutex;
|
||||
std::deque<pipes::buffer> queue_read;
|
||||
std::deque<pipes::buffer> queue_write;
|
||||
threads::Mutex execute_mutex; /* needs to be recursive! */
|
||||
|
||||
void processNextMessage(const std::chrono::system_clock::time_point& /* scheduled */);
|
||||
void registerMessageProcess();
|
||||
std::thread flush_thread;
|
||||
std::recursive_mutex close_lock;
|
||||
|
||||
std::shared_ptr<event::ProxiedEventEntry<WebClient>> event_handle_packet;
|
||||
struct {
|
||||
std::mutex mutex{};
|
||||
pipes::buffer target_header{};
|
||||
bool is_new_header{false};
|
||||
bool is_set{false};
|
||||
} whisper;
|
||||
private:
|
||||
void initialize();
|
||||
|
||||
//WS events
|
||||
void onWSConnected();
|
||||
void onWSDisconnected(const std::string& reason);
|
||||
void onWSMessage(const pipes::WSMessage&);
|
||||
protected:
|
||||
void disconnectFinal();
|
||||
void handleMessage(const std::string &);
|
||||
void handleMessageRead(int, short, void*);
|
||||
void handleMessageWrite(int, short, void*);
|
||||
void enqueue_raw_packet(const pipes::buffer_view& /* buffer */);
|
||||
|
||||
public:
|
||||
void send_voice_packet(const pipes::buffer_view &view, const VoicePacketFlags &flags) override;
|
||||
void send_voice_whisper_packet(const pipes::buffer_view &view, const VoicePacketFlags &flags) override;
|
||||
void processNextMessage(const std::chrono::system_clock::time_point& /* scheduled */);
|
||||
void registerMessageProcess();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<event::ProxiedEventEntry<WebClient>> event_handle_packet;
|
||||
|
||||
command_result handleCommand(Command &command) override;
|
||||
command_result handleCommandClientInit(Command &command) override;
|
||||
};
|
||||
}
|
||||
//WS events
|
||||
void onWSConnected();
|
||||
void onWSDisconnected(const std::string& reason);
|
||||
void onWSMessage(const pipes::WSMessage&);
|
||||
protected:
|
||||
void disconnectFinal();
|
||||
void handleMessage(const pipes::buffer_view&);
|
||||
|
||||
public:
|
||||
void send_voice_packet(const pipes::buffer_view &view, const VoicePacketFlags &flags) override;
|
||||
void send_voice_whisper_packet(const pipes::buffer_view &/* teamspeak packet */, const pipes::buffer_view &/* teaspeak packet */, const VoicePacketFlags &flags) override;
|
||||
|
||||
protected:
|
||||
|
||||
command_result handleCommand(Command &command) override;
|
||||
command_result handleCommandClientInit(Command &command) override;
|
||||
|
||||
command_result handleCommandSetWhisperTarget(Command &command);
|
||||
command_result handleCommandClearWhisperTarget(Command &command);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user