diff --git a/file/CMakeLists.txt b/file/CMakeLists.txt index 83f9fa0..72b0365 100644 --- a/file/CMakeLists.txt +++ b/file/CMakeLists.txt @@ -5,13 +5,22 @@ project(TeaSpeak-Files) #set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(TeaSpeak-FileServer STATIC - local_server/LocalFileServer.cpp + local_server/LocalFileProvider.cpp local_server/LocalFileSystem.cpp + local_server/LocalFileTransfer.cpp + local_server/LocalFileTransferClientWorker.cpp + local_server/LocalFileTransferDisk.cpp + local_server/LocalFileTransferNetwork.cpp local_server/clnpath.cpp ) target_link_libraries(TeaSpeak-FileServer PUBLIC TeaSpeak ${StringVariable_LIBRARIES_STATIC} stdc++fs - libevent::core libevent::pthreads) + libevent::core libevent::pthreads + DataPipes::core::static + openssl::ssl::shared + openssl::crypto::shared +) + target_include_directories(TeaSpeak-FileServer PUBLIC include/) add_executable(TeaSpeak-FileServerTest test/main.cpp) @@ -20,6 +29,7 @@ target_link_libraries(TeaSpeak-FileServerTest PUBLIC TeaSpeak-FileServer CXXTerminal::static #Static stdc++fs ) +target_compile_options(TeaSpeak-FileServerTest PUBLIC -static-libgcc -static-libstdc++) add_executable(FileServer-CLNText local_server/clnpath.cpp) target_compile_definitions(FileServer-CLNText PUBLIC -DCLN_EXEC) \ No newline at end of file diff --git a/file/include/files/FileServer.h b/file/include/files/FileServer.h index 19e7d11..5d2dcaa 100644 --- a/file/include/files/FileServer.h +++ b/file/include/files/FileServer.h @@ -8,6 +8,8 @@ #include #include +#define TRANSFER_KEY_LENGTH (32) + namespace ts::server::file { enum struct ExecuteStatus { UNKNOWN, @@ -70,6 +72,10 @@ namespace ts::server::file { 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: @@ -185,7 +191,7 @@ namespace ts::server::file { ClientId client_id{0}; ChannelId channel_id{0}; - std::string transfer_key{}; + char transfer_key[TRANSFER_KEY_LENGTH]{}; std::chrono::system_clock::time_point initialized_timestamp{}; enum Direction { DIRECTION_UNKNOWN, @@ -193,8 +199,11 @@ namespace ts::server::file { DIRECTION_DOWNLOAD } direction{DIRECTION_UNKNOWN}; - std::string transfer_hosts{}; /* comma separated list */ - uint16_t transfer_port{0}; + struct Address { + std::string hostname{}; + uint16_t port{0}; + }; + std::vector
server_addresses{}; enum TargetType { TARGET_TYPE_UNKNOWN, @@ -205,19 +214,24 @@ namespace ts::server::file { std::string target_file_path{}; int64_t max_bandwidth{-1}; - size_t expected_file_size{0}; + size_t expected_file_size{0}; /* incl. the offset! */ size_t file_offset{0}; + bool override_exiting{false}; }; struct TransferStatistics { - uint64_t bytes_send{0}; - uint64_t bytes_received{0}; + uint64_t network_bytes_send{0}; + uint64_t network_bytes_received{0}; - uint64_t delta_bytes_send{0}; - uint64_t delta_bytes_received{0}; + uint64_t delta_network_bytes_send{0}; + uint64_t delta_network_bytes_received{0}; - size_t current_offset{0}; - size_t total_size{0}; + uint64_t file_bytes_transferred{0}; + uint64_t delta_file_bytes_transferred{0}; + + size_t file_start_offset{0}; + size_t file_current_offset{0}; + size_t file_total_size{0}; }; struct TransferInitError { @@ -239,10 +253,17 @@ namespace ts::server::file { struct TransferError { enum Type { UNKNOWN, + + TRANSFER_TIMEOUT, + DISK_IO_ERROR, + DISK_TIMEOUT, + DISK_INITIALIZE_ERROR, + NETWORK_IO_ERROR, - UNEXPECTED_CLIENT_EOF + UNEXPECTED_CLIENT_DISCONNECT, + UNEXPECTED_DISK_EOF } error_type{UNKNOWN}; std::string error_message{}; }; @@ -262,20 +283,20 @@ namespace ts::server::file { virtual std::shared_ptr>> initialize_icon_transfer(Transfer::Direction /* direction */, ServerId /* server */, const TransferInfo& /* info */) = 0; virtual std::shared_ptr>> initialize_avatar_transfer(Transfer::Direction /* direction */, ServerId /* server */, const TransferInfo& /* info */) = 0; - virtual std::shared_ptr> stop_transfer(transfer_id) = 0; + virtual std::shared_ptr> stop_transfer(transfer_id /* id */, bool /* flush */) = 0; - std::function&)> callback_transfer_timeout{}; /* client has not connected to that transfer */ + std::function&)> callback_transfer_registered{}; /* transfer has been registered */ std::function&)> callback_transfer_started{}; /* transfer has been started */ std::function&)> callback_transfer_finished{}; /* transfer has been finished */ - std::function&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transfering the data */ + std::function&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */ std::function&, const TransferStatistics&)> callback_transfer_statistics{}; - private: }; } class AbstractFileServer { public: [[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0; + [[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0; private: }; diff --git a/file/local_server/LocalFileProvider.cpp b/file/local_server/LocalFileProvider.cpp new file mode 100644 index 0000000..37e1ade --- /dev/null +++ b/file/local_server/LocalFileProvider.cpp @@ -0,0 +1,73 @@ +// +// Created by WolverinDEV on 28/04/2020. +// + +#include +#include "LocalFileProvider.h" + +using namespace ts::server; +using LocalFileServer = file::LocalFileProvider; + +std::shared_ptr server_instance{}; +bool file::initialize(std::string &error) { + server_instance = std::make_shared(); + if(!server_instance->initialize(error)) { + server_instance = nullptr; + return false; + } + return true; +} + +void file::finalize() { + auto server = std::exchange(server_instance, nullptr); + if(!server) return; + + server->finalize(); +} + +std::shared_ptr file::server() { + return server_instance; +} + +LocalFileServer::LocalFileProvider() : file_system_{}, file_transfer_{this->file_system_} {} +LocalFileServer::~LocalFileProvider() {} + +bool LocalFileServer::initialize(std::string &error) { + if(!this->file_system_.initialize(error, "file-root/")) + return false; + + + std::deque> bindings{}; + { + auto binding = std::make_shared(); + + 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)) { + error = "transfer server startup failed"; + this->file_system_.finalize(); + return false; + } + return true; +} + +void LocalFileServer::finalize() { + this->file_transfer_.stop(); + this->file_system_.finalize(); +} + +file::filesystem::AbstractProvider &LocalFileServer::file_system() { + return this->file_system_; +} + +file::transfer::AbstractProvider & LocalFileServer::file_transfer() { + return this->file_transfer_; +} \ No newline at end of file diff --git a/file/local_server/LocalFileProvider.h b/file/local_server/LocalFileProvider.h new file mode 100644 index 0000000..052b70f --- /dev/null +++ b/file/local_server/LocalFileProvider.h @@ -0,0 +1,494 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRANSFER_MAX_CACHED_BYTES (1024 * 1024 * 1) // Buffer up to 1mb + +namespace ts::server::file { + namespace 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(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> initialize_server(ServerId /* server */) override; + std::shared_ptr> delete_server(ServerId /* server */) override; + + std::shared_ptr + query_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override; + + std::shared_ptr> + create_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override; + + std::shared_ptr> + delete_channel_file(ServerId id, ChannelId channelId, const std::string &string) override; + + std::shared_ptr> + rename_channel_file(ServerId id, ChannelId channelId, const std::string &, const std::string &) override; + + std::shared_ptr query_icon_directory(ServerId id) override; + + std::shared_ptr> + delete_icon(ServerId id, const std::string &string) override; + + std::shared_ptr query_avatar_directory(ServerId id) override; + + std::shared_ptr> + 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> + delete_file(const fs::path& /* base */, const std::string &string); + + [[nodiscard]] std::shared_ptr + query_directory(const fs::path& /* base */, const std::string &string, bool); +#endif + + template + std::shared_ptr> create_execute_response() { + return std::make_shared>(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 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::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::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::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; + } + }; + + /* all variables are locked via the state_mutex */ + struct FileClient : std::enable_shared_from_this { + LocalFileTransfer* handle; + std::shared_ptr 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 + }; + + struct { + bool file_locked{false}; + std::string absolute_path{}; + +#if 0 + struct event* io_process{nullptr}; /* either a read event or write event */ +#endif + 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{}; + + /* 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 */); + + [[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 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 + }; + + struct NetworkBinding : std::enable_shared_from_this { + 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>& /* bindings */); + void stop(); + + std::shared_ptr>> + initialize_channel_transfer(Transfer::Direction direction, ServerId id, ChannelId channelId, + const TransferInfo &info) override; + + std::shared_ptr>> + initialize_icon_transfer(Transfer::Direction direction, ServerId id, const TransferInfo &info) override; + + std::shared_ptr>> + initialize_avatar_transfer(Transfer::Direction direction, ServerId id, const TransferInfo &info) override; + + std::shared_ptr> stop_transfer(transfer_id id, bool) override; + private: + enum struct DiskIOLoopState { + STOPPED, + RUNNING, + + STOPPING, + FORCE_STOPPING + }; + filesystem::LocalFileSystem& file_system_; + + std::atomic 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> transfers_{}; + std::deque> 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 { + bool active{false}; + std::thread dispatch_thread{}; + struct event_base* event_base{nullptr}; + + std::deque> 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 + std::shared_ptr> create_execute_response() { + return std::make_shared>(this->result_notify_mutex, this->result_notify_cv); + } + + std::shared_ptr>> + 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& /* client */, std::unique_lock& /* state lock */, bool /* flush */); + + [[nodiscard]] NetworkInitializeResult initialize_networking(const std::shared_ptr& /* client */, int /* file descriptor */); + /* might block 'till all IO operations have been succeeded */ + void finalize_networking(const std::shared_ptr& /* client */, std::unique_lock& /* state lock */); + + [[nodiscard]] FileInitializeResult initialize_file_io(const std::shared_ptr& /* client */); + void finalize_file_io(const std::shared_ptr& /* client */, std::unique_lock& /* state lock */); + + void enqueue_disk_io(const std::shared_ptr& /* client */); + void execute_disk_io(const std::shared_ptr& /* client */); + + void report_transfer_statistics(const std::shared_ptr& /* client */); + + 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& /* client */, const char* /* buffer */, size_t /* bytes */); + [[nodiscard]] TransferKeyApplyResult handle_transfer_key_provided(const std::shared_ptr& /* client */); + }; + } + + class LocalFileProvider : public AbstractFileServer { + public: + LocalFileProvider(); + virtual ~LocalFileProvider(); + + [[nodiscard]] bool initialize(std::string& /* error */); + void finalize(); + + filesystem::AbstractProvider &file_system() override; + transfer::AbstractProvider &file_transfer() override; + + private: + filesystem::LocalFileSystem file_system_; + transfer::LocalFileTransfer file_transfer_; + }; +} \ No newline at end of file diff --git a/file/local_server/LocalFileServer.cpp b/file/local_server/LocalFileServer.cpp deleted file mode 100644 index 8d77270..0000000 --- a/file/local_server/LocalFileServer.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// -// Created by WolverinDEV on 28/04/2020. -// - -#include "./LocalFileServer.h" - -using namespace ts::server; -using LocalFileServer = file::LocalFileServer; - -std::shared_ptr server_instance{}; -bool file::initialize(std::string &error) { - server_instance = std::make_shared(); - if(!server_instance->initialize(error)) { - server_instance = nullptr; - return false; - } - return true; -} - -void file::finalize() { - auto server = std::exchange(server_instance, nullptr); - if(!server) return; - - server->finalize(); -} - -std::shared_ptr file::server() { - return server_instance; -} - -LocalFileServer::~LocalFileServer() {} - -bool LocalFileServer::initialize(std::string &error) { - if(!this->file_system_.initialize(error, "file-root/")) - return false; - return true; -} - -void LocalFileServer::finalize() { - -} - -file::filesystem::AbstractProvider &LocalFileServer::file_system() { - return this->file_system_; -} diff --git a/file/local_server/LocalFileServer.h b/file/local_server/LocalFileServer.h deleted file mode 100644 index 882f504..0000000 --- a/file/local_server/LocalFileServer.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - - -#include -#include - -namespace ts::server::file { - namespace filesystem { -#ifdef FS_INCLUDED - namespace fs = std::experimental::filesystem; -#endif - - class LocalFileSystem : public filesystem::AbstractProvider { - using FileModifyError = filesystem::FileModifyError; - using DirectoryModifyError = filesystem::DirectoryModifyError; - public: - virtual ~LocalFileSystem(); - - bool initialize(std::string & /* error */, const std::string & /* root path */); - void finalize(); - - void lock_file(const std::string& /* path */); - void unlock_file(const std::string& /* path */); - - [[nodiscard]] inline const auto &root_path() const { return this->root_path_; } - - std::shared_ptr> initialize_server(ServerId /* server */) override; - std::shared_ptr> delete_server(ServerId /* server */) override; - - std::shared_ptr - query_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override; - - std::shared_ptr> - create_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override; - - std::shared_ptr> - delete_channel_file(ServerId id, ChannelId channelId, const std::string &string) override; - - std::shared_ptr> - rename_channel_file(ServerId id, ChannelId channelId, const std::string &, const std::string &) override; - - std::shared_ptr query_icon_directory(ServerId id) override; - - std::shared_ptr> - delete_icon(ServerId id, const std::string &string) override; - - std::shared_ptr query_avatar_directory(ServerId id) override; - - std::shared_ptr> - 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> - delete_file(const fs::path& /* base */, const std::string &string); - - [[nodiscard]] std::shared_ptr - query_directory(const fs::path& /* base */, const std::string &string, bool); -#endif - - template - std::shared_ptr> create_execute_response() { - return std::make_shared>(this->result_notify_mutex, this->result_notify_cv); - } - - std::mutex result_notify_mutex{}; - std::condition_variable result_notify_cv{}; - - std::string root_path_{}; - - std::mutex locked_files_mutex{}; - std::deque locked_files_{}; - }; - } - - class LocalFileServer : public AbstractFileServer { - public: - virtual ~LocalFileServer(); - - [[nodiscard]] bool initialize(std::string& /* error */); - void finalize(); - - filesystem::AbstractProvider &file_system() override; - - private: - filesystem::LocalFileSystem file_system_{}; - }; -} \ No newline at end of file diff --git a/file/local_server/LocalFileSystem.cpp b/file/local_server/LocalFileSystem.cpp index cc5285f..2295fd1 100644 --- a/file/local_server/LocalFileSystem.cpp +++ b/file/local_server/LocalFileSystem.cpp @@ -5,7 +5,7 @@ #define FS_INCLUDED #include -#include "./LocalFileServer.h" +#include "LocalFileProvider.h" #include "clnpath.h" using namespace ts::server::file; @@ -21,7 +21,7 @@ bool LocalFileSystem::initialize(std::string &error_message, const std::string & if(!fs::exists(root_path, error)) { if(error) - logWarning(0, "Failed to check root path existence. Assuming it does not exist. ({}/{})", error.value(), error.message()); + logWarning(LOG_FT, "Failed to check root path existence. Assuming it does not exist. ({}/{})", error.value(), error.message()); if(!fs::create_directories(root_path, error) || error) { error_message = "Failed to create root file system at " + root_path_string + ": " + std::to_string(error.value()) + "/" + error.message(); return false; @@ -29,7 +29,7 @@ bool LocalFileSystem::initialize(std::string &error_message, const std::string & } auto croot = clnpath(fs::absolute(root_path).string()); - logMessage(0, "Started file system root at {}", croot); + logMessage(LOG_FT, "Started file system root at {}", croot); this->root_path_ = croot; return true; } @@ -56,6 +56,7 @@ bool LocalFileSystem::exceeds_base_path(const fs::path &base, const fs::path &ta bool LocalFileSystem::is_any_file_locked(const fs::path &base, const std::string &path, std::string &locked_file) { auto c_path = clnpath(fs::absolute(base / fs::u8path(path)).string()); + std::lock_guard lock{this->locked_files_mutex}; for(const auto& lfile : this->locked_files_) { if(lfile.starts_with(c_path)) { locked_file = lfile.substr(base.string().length()); @@ -66,6 +67,46 @@ 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) { + fs::path target_path{}; + switch (type) { + case FileCategory::CHANNEL: + target_path = this->server_channel_path(sid, cid) / path; + break; + case FileCategory::ICON: + target_path = this->server_path(sid) / "icons" / path; + break; + case FileCategory::AVATAR: + target_path = this->server_path(sid) / "avatars" / path; + break; + } + + return clnpath(fs::absolute(target_path).string()); +} + +std::string LocalFileSystem::absolute_avatar_path(ServerId 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) { + return this->target_file_path(FileCategory::ICON, sid, 0, path); +} + +std::string LocalFileSystem::absolute_channel_path(ServerId sid, ChannelId cid, const std::string &path) { + return this->target_file_path(FileCategory::CHANNEL, sid, cid, path); +} + +void LocalFileSystem::lock_file(const std::string &c_path) { + std::lock_guard lock{this->locked_files_mutex}; + this->locked_files_.push_back(c_path); +} + +void LocalFileSystem::unlock_file(const std::string &c_path) { + std::lock_guard lock{this->locked_files_mutex}; + + 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> LocalFileSystem::initialize_server(ServerId id) { auto path = this->server_path(id); std::error_code error{}; @@ -122,7 +163,7 @@ std::shared_ptr LocalFileSystem::query_directory(con response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, ""); return response; } else if(error) { - logWarning(0, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message()); response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, ""); return response; } @@ -131,7 +172,7 @@ std::shared_ptr LocalFileSystem::query_directory(con response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, ""); return response; } else if(error) { - logWarning(0, "Failed to check for directory at {}: {}. Assuming its not a directory.", target_path.string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to check for directory at {}: {}. Assuming its not a directory.", target_path.string(), error.value(), error.message()); response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, ""); return response; } @@ -140,7 +181,7 @@ std::shared_ptr LocalFileSystem::query_directory(con for(auto& entry : fs::directory_iterator(target_path, error)) { auto status = entry.status(error); if(error) { - logWarning(0, "Failed to query file status for {} ({}/{}). Skipping entry for directory query.", entry.path().string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to query file status for {} ({}/{}). Skipping entry for directory query.", entry.path().string(), error.value(), error.message()); continue; } @@ -151,7 +192,7 @@ std::shared_ptr LocalFileSystem::query_directory(con dentry.modified_at = fs::last_write_time(entry.path(), error); if(error) - logWarning(0, "Failed to query last write time for directory {} ({}/{})", entry.path().string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", entry.path().string(), error.value(), error.message()); dentry.size = 0; } else if(status.type() == fs::file_type::regular) { auto& dentry = entries.emplace_back(); @@ -160,12 +201,12 @@ std::shared_ptr LocalFileSystem::query_directory(con dentry.modified_at = fs::last_write_time(entry.path(), error); if(error) - logWarning(0, "Failed to query last write time for file {} ({}/{}).", entry.path().string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{}).", entry.path().string(), error.value(), error.message()); dentry.size = fs::file_size(entry.path(), error); if(error) - logWarning(0, "Failed to query size for file {} ({}/{}).", entry.path().string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to query size for file {} ({}/{}).", entry.path().string(), error.value(), error.message()); } else { - logWarning(0, "Directory query listed an unknown file type for file {} ({}).", entry.path().string(), (int) status.type()); + logWarning(LOG_FT, "Directory query listed an unknown file type for file {} ({}).", entry.path().string(), (int) status.type()); } } if(error && entries.empty()) { @@ -203,7 +244,7 @@ std::shared_ptr> LocalFileSystem::create_c response->emplace_fail(DirectoryModifyErrorType::PATH_ALREADY_EXISTS, ""); return response; } else if(error) { - logWarning(0, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message()); } if(!fs::create_directories(target_path, error) || error) { @@ -237,7 +278,7 @@ std::shared_ptr> LocalFileSystem::rename_channe response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, ""); return response; } else if(error) { - logWarning(0, "Failed to check for file at {}: {}. Assuming it does not exists.", current_path.string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", current_path.string(), error.value(), error.message()); response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, ""); return response; } @@ -246,7 +287,7 @@ std::shared_ptr> LocalFileSystem::rename_channe response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, ""); return response; } else if(error) { - logWarning(0, "Failed to check for file at {}: {}. Assuming it does exists.", current_path.string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", current_path.string(), error.value(), error.message()); response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, ""); return response; } @@ -277,7 +318,7 @@ std::shared_ptr> LocalFileSystem::delete_file(c response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, ""); return response; } else if(error) { - logWarning(0, "Failed to check for file at {}: {}. Assuming it does exists.", target_path.string(), error.value(), error.message()); + 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; } diff --git a/file/local_server/LocalFileTransfer.cpp b/file/local_server/LocalFileTransfer.cpp new file mode 100644 index 0000000..fcc622e --- /dev/null +++ b/file/local_server/LocalFileTransfer.cpp @@ -0,0 +1,208 @@ +// +// Created by WolverinDEV on 04/05/2020. +// + +#include +#include +#include +#include +#include "./LocalFileProvider.h" +#include "LocalFileProvider.h" + +using namespace ts::server::file; +using namespace ts::server::file::transfer; + +Buffer* transfer::allocate_buffer(size_t size) { + auto total_size = sizeof(Buffer) + size; + auto buffer = (Buffer*) malloc(total_size); + new (buffer) Buffer{}; + return buffer; +} + +void transfer::free_buffer(Buffer* buffer) { + buffer->~Buffer(); + free(buffer); +} + +FileClient::~FileClient() { + auto head = this->buffer.buffer_head; + while (head) { + auto next = head->next; + free_buffer(head); + head = next; + } + + assert(!this->file.file_descriptor); + assert(!this->file.currently_processing); + assert(!this->file.next_client); + + assert(!this->networking.event_read); + assert(!this->networking.event_write); + + assert(this->state == STATE_DISCONNECTED); +} + +LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem &fs) : file_system_{fs} {} +LocalFileTransfer::~LocalFileTransfer() = default; + +bool LocalFileTransfer::start(const std::deque>& bindings) { + (void) this->start_client_worker(); + + { + auto start_result = this->start_disk_io(); + switch (start_result) { + case DiskIOStartResult::SUCCESS: + break; + case DiskIOStartResult::OUT_OF_MEMORY: + logError(LOG_FT, "Failed to start disk worker (Out of memory)"); + goto error_exit_disk; + default: + logError(LOG_FT, "Failed to start disk worker ({})", (int) start_result); + goto error_exit_disk; + } + } + + + { + 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; + } + } + + return true; + error_exit_network: + this->shutdown_networking(); + + error_exit_disk: + this->shutdown_disk_io(); + this->shutdown_client_worker(); + return false; +} + +void LocalFileTransfer::stop() { + this->shutdown_networking(); + this->shutdown_disk_io(); + this->shutdown_client_worker(); +} + +std::shared_ptr>> 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>> 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>> 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>> LocalFileTransfer::initialize_transfer( + Transfer::Direction direction, ServerId sid, ChannelId cid, + Transfer::TargetType ttype, + const TransferInfo &info) { + auto response = this->create_execute_response>(); + + /* TODO: test for a transfer limit */ + + auto transfer = std::make_shared(); + transfer->server_transfer_id = ++this->current_transfer_id; + transfer->server_id = sid; + transfer->channel_id = cid; + transfer->target_type = ttype; + transfer->direction = direction; + + transfer->client_id = 0; /* must be provided externally */ + transfer->client_transfer_id = 0; /* must be provided externally */ + + transfer->server_addresses.reserve(this->network.bindings.size()); + for(auto& binding : this->network.bindings) { + if(!binding->file_descriptor) continue; + + transfer->server_addresses.emplace_back(Transfer::Address{binding->hostname, net::port(binding->address)}); + } + + transfer->target_file_path = info.file_path; + transfer->file_offset = info.file_offset; + transfer->expected_file_size = info.expected_file_size; + transfer->max_bandwidth = info.max_bandwidth; + + constexpr static std::string_view kTokenCharacters{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}; + for(auto& c : transfer->transfer_key) + c = kTokenCharacters[transfer_random_token_generator() % kTokenCharacters.length()]; + transfer->transfer_key[0] = (char) 'r'; /* (114) */ /* a non valid SSL header type to indicate that we're using a file transfer key and not doing a SSL handshake */ + transfer->transfer_key[1] = (char) 'a'; /* ( 97) */ + transfer->transfer_key[2] = (char) 'w'; /* (119) */ + + transfer->initialized_timestamp = std::chrono::system_clock::now(); + + { + std::lock_guard tlock{this->transfers_mutex}; + this->pending_transfers.push_back(transfer); + } + + if(auto callback{this->callback_transfer_registered}; callback) + callback(transfer); + + response->emplace_success(std::move(transfer)); + return response; +} + +std::shared_ptr> LocalFileTransfer::stop_transfer(transfer_id id, bool flush) { + auto response = this->create_execute_response(); + + std::shared_ptr transfer{}; + std::shared_ptr connected_transfer{}; + + { + std::lock_guard tlock{this->transfers_mutex}; + + auto ct_it = std::find_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr& t) { + return t->transfer && t->transfer->server_transfer_id == id; + }); + 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& t) { + return t->server_transfer_id == id; + }); + if(t_it != this->pending_transfers.end()) { + transfer = *t_it; + this->pending_transfers.erase(t_it); + } + } + } + + if(!transfer) { + if(connected_transfer) + transfer = connected_transfer->transfer; + else { + response->emplace_fail(TransferActionError{TransferActionError::UNKNOWN_TRANSFER, ""}); + return response; + } + } + + if(connected_transfer) { + 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 { + logMessage(LOG_FT, "Removing pending file transfer for id {}", id); + } + + response->emplace_success(); + return response; +} \ No newline at end of file diff --git a/file/local_server/LocalFileTransferClientWorker.cpp b/file/local_server/LocalFileTransferClientWorker.cpp new file mode 100644 index 0000000..bc54bd3 --- /dev/null +++ b/file/local_server/LocalFileTransferClientWorker.cpp @@ -0,0 +1,207 @@ +// +// Created by WolverinDEV on 04/05/2020. +// + +#include +#include +#include +#include "./LocalFileProvider.h" + +using namespace ts::server::file; +using namespace ts::server::file::transfer; + +ClientWorkerStartResult LocalFileTransfer::start_client_worker() { + assert(!this->disconnect.active); + this->disconnect.active = true; + + this->disconnect.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_client_worker, this); + return ClientWorkerStartResult::SUCCESS; +} + +void LocalFileTransfer::shutdown_client_worker() { + if(!this->disconnect.active) return; + this->disconnect.active = false; + + this->disconnect.notify_cv.notify_all(); + if(this->disconnect.dispatch_thread.joinable()) + this->disconnect.dispatch_thread.join(); + + { + std::unique_lock tlock{this->transfers_mutex}; + if(!this->transfers_.empty()) + logWarning(LOG_FT, "Shutting down disconnect worker even thou we still have some active clients. This could cause memory leaks."); + } +} + +void LocalFileTransfer::disconnect_client(const std::shared_ptr &client, std::unique_lock& state_lock, bool flush) { + assert(state_lock.owns_lock()); + + if(client->state == FileClient::STATE_DISCONNECTED || (client->state == FileClient::STATE_DISCONNECTING && 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->timings.disconnecting = std::chrono::system_clock::now(); + if(flush) { + 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); + + /* 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() + std::chrono::seconds{10}; + } + } else { + del_ev_noblock(client->networking.event_read); + del_ev_noblock(client->networking.event_write); + del_ev_noblock(client->networking.event_throttle); + + this->disconnect.notify_cv.notify_one(); + } + +#undef del_ev_noblock +} + +void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) { + auto provider = reinterpret_cast(ptr_transfer); + + while(provider->disconnect.active) { + { + std::unique_lock dlock{provider->disconnect.mutex}; + provider->disconnect.notify_cv.wait_for(dlock, std::chrono::seconds{1}); + } + /* run the disconnect worker at least once before exiting */ + + /* transfer statistics */ + { + std::unique_lock tlock{provider->transfers_mutex}; + auto transfers = provider->transfers_; + tlock.unlock(); + for(const auto& transfer : transfers) { + 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) */ + default: + continue; + } + + provider->report_transfer_statistics(transfer->shared_from_this()); + } + } + + { + std::deque> timeouted_transfers{}; + + { + std::unique_lock tlock{provider->transfers_mutex}; + + 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& t) { + return t->initialized_timestamp + std::chrono::seconds{100} < now; //FIXME: Decrease to 10 again! + }); + 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, "" }); + } + + if(!timeouted_transfers.empty()) + logMessage(LOG_FT, "Removed {} pending transfers because no request has been made for them.", timeouted_transfers.size()); + } + + + { + std::deque> disconnected_clients{}; + { + std::unique_lock tlock{provider->transfers_mutex}; + + auto now = std::chrono::system_clock::now(); + std::copy_if(provider->transfers_.begin(), provider->transfers_.end(), std::back_inserter(disconnected_clients), [&](const std::shared_ptr& t) { + std::shared_lock slock{t->state_mutex}; + if(t->state == FileClient::STATE_DISCONNECTED) { + return true; + } else if(t->state == FileClient::STATE_AWAITING_KEY) { + return t->timings.connected + std::chrono::seconds{10} < now; + } 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) { + return t->timings.disconnecting + std::chrono::seconds{30} < now; + } + return false; + }); + provider->transfers_.erase(std::remove_if(provider->transfers_.begin(), provider->transfers_.end(), [&](const auto& t) { + return std::find(disconnected_clients.begin(), disconnected_clients.end(), t) != disconnected_clients.end(); + }), provider->transfers_.end()); + } + + for(auto& client : disconnected_clients) { + switch(client->state) { + case FileClient::STATE_AWAITING_KEY: + logMessage(LOG_FT, "{} Received no key. Dropping client.", client->log_prefix()); + 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, "" }); + break; + case FileClient::STATE_DISCONNECTING: + logMessage(LOG_FT, "{} Failed to flush connection. Dropping client", client->log_prefix()); + break; + default: + break; + } + { + std::unique_lock slock{client->state_mutex}; + client->state = FileClient::STATE_DISCONNECTED; + provider->finalize_file_io(client, slock); + provider->finalize_networking(client, slock); + } + + debugMessage(LOG_FT, "{} Destroying transfer.", client->log_prefix()); + } + } + } +} + +void LocalFileTransfer::report_transfer_statistics(const std::shared_ptr &client) { + auto callback{this->callback_transfer_statistics}; + if(!callback) return; + + 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.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_file_bytes_transferred = stats.file_bytes_transferred - std::exchange(client->statistics.last_file_bytes_transferred, stats.file_bytes_transferred); + + stats.file_start_offset = client->transfer->file_offset; + stats.file_current_offset = client->statistics.file_bytes_transferred + client->transfer->file_offset; + stats.file_total_size = client->transfer->expected_file_size; + + callback(client->transfer, stats); +} \ No newline at end of file diff --git a/file/local_server/LocalFileTransferDisk.cpp b/file/local_server/LocalFileTransferDisk.cpp new file mode 100644 index 0000000..0cadb50 --- /dev/null +++ b/file/local_server/LocalFileTransferDisk.cpp @@ -0,0 +1,473 @@ +// +// Created by WolverinDEV on 04/05/2020. +// + +#include +#include +#include +#include +#include "./LocalFileProvider.h" +#include "./duration_utils.h" +#include "LocalFileProvider.h" + +using namespace ts::server::file; +using namespace ts::server::file::transfer; +namespace fs = std::experimental::filesystem; + +DiskIOStartResult LocalFileTransfer::start_disk_io() { + assert(this->disk_io.state == DiskIOLoopState::STOPPED); + + this->disk_io.state = DiskIOLoopState::RUNNING; + this->disk_io.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_disk_io, this); + return DiskIOStartResult::SUCCESS; +} + +void LocalFileTransfer::shutdown_disk_io() { + if(this->disk_io.state == DiskIOLoopState::STOPPED) return; + + this->disk_io.state = DiskIOLoopState::STOPPING; + { + std::unique_lock qlock{this->disk_io.queue_lock}; + this->disk_io.notify_work_awaiting.notify_all(); + while(this->disk_io.queue_head) + this->disk_io.notify_client_processed.wait_for(qlock, std::chrono::seconds{10}); + + if(this->disk_io.queue_head) { + logWarning(0, "Failed to flush disk IO. Force aborting."); + this->disk_io.state = DiskIOLoopState::FORCE_STOPPING; + this->disk_io.notify_work_awaiting.notify_all(); + this->disk_io.notify_client_processed.wait(qlock); + } + } + + if(this->disk_io.dispatch_thread.joinable()) + this->disk_io.dispatch_thread.join(); + + this->disk_io.state = DiskIOLoopState::STOPPED; +} + +void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) { + auto provider = reinterpret_cast(provider_ptr); + + std::shared_ptr client{}; + while(true) { + { + std::unique_lock qlock{provider->disk_io.queue_lock}; + if(client) { + client->file.currently_processing = false; + provider->disk_io.notify_client_processed.notify_all(); + client = nullptr; + } + + 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(); + + provider->disk_io.queue_head = provider->disk_io.queue_head->file.next_client; + if(!provider->disk_io.queue_head) + provider->disk_io.queue_tail = &provider->disk_io.queue_head; + } + + if(provider->disk_io.state != DiskIOLoopState::RUNNING) { + if(provider->disk_io.state == DiskIOLoopState::STOPPING) { + if(!client) + break; + /* break only if all clients have been flushed */ + } else { + /* force stopping without any flushing */ + auto fclient = &*client; + while(fclient) + fclient = std::exchange(fclient->file.next_client, nullptr); + + provider->disk_io.queue_head = nullptr; + provider->disk_io.queue_tail = &provider->disk_io.queue_head; + break; + } + } + + if(!client) + continue; + + client->file.currently_processing = true; + client->file.next_client = nullptr; + } + + provider->execute_disk_io(client); + } + provider->disk_io.notify_client_processed.notify_all(); +} + +FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr &transfer) { + FileInitializeResult result{FileInitializeResult::SUCCESS}; + assert(transfer->transfer); + + std::shared_lock slock{transfer->state_mutex}; + auto& file_data = transfer->file; + assert(!file_data.file_descriptor); + assert(!file_data.next_client); + + { + 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)) { + 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()); + result = FileInitializeResult::FILE_SYSTEM_ERROR; + goto error_exit; + } + } else if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) { + open_flags = (unsigned) O_WRONLY | (unsigned) O_CREAT; + if(transfer->transfer->override_exiting) + open_flags |= (unsigned) O_TRUNC; + } else { + return FileInitializeResult::INVALID_TRANSFER_DIRECTION; + } + + file_data.file_descriptor = open(file_data.absolute_path.c_str(), (int) open_flags, 0644); + if(file_data.file_descriptor <= 0) { + const auto errno_ = errno; + switch (errno_) { + case EACCES: + 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); + result = FileInitializeResult::FILE_SYSTEM_ERROR; + break; + case EISDIR: + result = FileInitializeResult::FILE_IS_A_DIRECTORY; + break; + case EMFILE: + result = FileInitializeResult::PROCESS_FILE_LIMIT_REACHED; + break; + case ENFILE: + result = FileInitializeResult::SYSTEM_FILE_LIMIT_REACHED; + break; + case ETXTBSY: + result = FileInitializeResult::FILE_IS_BUSY; + break; + case EROFS: + 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_)); + result = FileInitializeResult::FILE_SYSTEM_ERROR; + break; + } + goto error_exit; + } + } + + this->file_system_.lock_file(transfer->file.absolute_path); + transfer->file.file_locked = true; + + if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) { + if(ftruncate(file_data.file_descriptor, transfer->transfer->expected_file_size) != 0) { + 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); + result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE; + goto error_exit; + + case EFBIG: + result = FileInitializeResult::FILE_TOO_LARGE; + goto error_exit; + + case EIO: + logWarning(LOG_FT, "{} A disk IO error occurred while resizing file {}.", transfer->log_prefix(), file_data.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); + 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_)); + } + } + } else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) { + auto file_size = lseek(file_data.file_descriptor, 0, SEEK_END); + if(file_size != transfer->transfer->expected_file_size) { + logWarning(LOG_FT, "{} Expected target file to be of size {}, but file is actually of size {}", transfer->log_prefix(), transfer->transfer->expected_file_size, file_size); + result = FileInitializeResult::FILE_SIZE_MISMATCH; + goto error_exit; + } + } + { + auto new_pos = lseek(file_data.file_descriptor, transfer->transfer->file_offset, SEEK_SET); + if(new_pos < 0) { + logWarning(LOG_FT, "{} Failed to seek to target file offset ({}): {}/{}", transfer->log_prefix(), transfer->transfer->file_offset, errno, strerror(errno)); + result = FileInitializeResult::FILE_SEEK_FAILED; + goto error_exit; + } else if(new_pos != transfer->transfer->file_offset) { + logWarning(LOG_FT, "{} File rw offset mismatch after seek. Expected {} but received {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos); + 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); + } + + return FileInitializeResult::SUCCESS; + error_exit: + if(std::exchange(transfer->file.file_locked, false)) + this->file_system_.unlock_file(transfer->file.absolute_path); + + if(file_data.file_descriptor > 0) + ::close(file_data.file_descriptor); + file_data.file_descriptor = 0; + + return result; +} + +void LocalFileTransfer::finalize_file_io(const std::shared_ptr &transfer, + std::unique_lock &state_lock) { + assert(state_lock.owns_lock()); + + auto& file_data = transfer->file; + + state_lock.unlock(); + { + std::unique_lock dlock{this->disk_io.queue_lock}; + while(true) { + if(file_data.currently_processing) { + this->disk_io.notify_client_processed.wait(dlock); + 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; + + } + 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(file_data.file_descriptor > 0) + ::close(file_data.file_descriptor); + file_data.file_descriptor = 0; +} + +void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr &client) { + if(!client->file.file_descriptor) + return; + + if(!client->transfer) + return; + + if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) { + if(client->state != FileClient::STATE_TRANSFERRING) + return; + + if(client->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 */ + /* + if(client->buffer.bytes == 0) + return; + */ + } + + std::lock_guard dlock{this->disk_io.queue_lock}; + 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; + + this->disk_io.notify_work_awaiting.notify_all(); +} + +void LocalFileTransfer::execute_disk_io(const std::shared_ptr &client) { + if(!client->transfer) return; + + if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) { + Buffer* buffer{nullptr}; + size_t buffer_left_size{0}; + + while(true) { + { + std::lock_guard block{client->buffer.mutex}; + buffer = client->buffer.buffer_head; + buffer_left_size = client->buffer.bytes; + } + if(!buffer) { + assert(buffer_left_size == 0); + break; + } + + assert(buffer->offset < buffer->length); + auto written = ::write(client->file.file_descriptor, buffer->data + buffer->offset, buffer->length - buffer->offset); + 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 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) }); + + { + std::unique_lock slock{client->state_mutex}; + client->handle->disconnect_client(client, slock, true); + } + } else { + if(errno == EAGAIN) { + //TODO: Timeout? + this->enqueue_disk_io(client); + break; + } + + auto offset_written = client->statistics.disk_bytes_write + 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) }); + + { + std::unique_lock slock{client->state_mutex}; + client->handle->disconnect_client(client, slock, true); + } + } + return; + } else { + buffer->offset += written; + assert(buffer->offset <= buffer->length); + if(buffer->length == buffer->offset) { + { + std::lock_guard block{client->buffer.mutex}; + client->buffer.buffer_head = buffer->next; + if(!buffer->next) + client->buffer.buffer_tail = &client->buffer.buffer_head; + + assert(client->buffer.bytes >= written); + client->buffer.bytes -= written; + buffer_left_size = client->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; + (void) buffer_left_size; /* trick my IDE here a bit */ + } + + client->statistics.disk_bytes_write += written; + } + } + + 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); + } + + if(client->state == FileClient::STATE_TRANSFERRING && buffer_left_size < TRANSFER_MAX_CACHED_BYTES / 2) { + if(client->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) { + while(true) { + constexpr auto buffer_capacity{4096}; + char buffer[buffer_capacity]; + + auto read = ::read(client->file.file_descriptor, buffer, buffer_capacity); + if(read <= 0) { + if(read == 0) { + /* EOF */ + auto offset_send = client->statistics.disk_bytes_read + 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)); + } else { + auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR); + 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); + } + } else { + if(errno == EAGAIN) { + this->enqueue_disk_io(client); + return; + } + + logWarning(LOG_FT, "{} Failed to read from file {} ({}/{}). Aborting transfer.", client->log_prefix(), client->file.absolute_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); + } + } + return; + } else { + auto buffer_full = client->send_file_bytes(buffer, read); + client->statistics.disk_bytes_read += read; + client->statistics.file_bytes_transferred += 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); + break; + } + + /* we've stuff to write again, yeahr */ + client->add_network_write_event(false); + } + } + } else { + logError(LOG_FT, "{} Disk IO scheduled, but transfer direction is unknown.", client->log_prefix()); + } +} \ No newline at end of file diff --git a/file/local_server/LocalFileTransferNetwork.cpp b/file/local_server/LocalFileTransferNetwork.cpp new file mode 100644 index 0000000..8a7da1e --- /dev/null +++ b/file/local_server/LocalFileTransferNetwork.cpp @@ -0,0 +1,843 @@ +// +// Created by WolverinDEV on 04/05/2020. +// + +#include +#include +#include +#include +#include +#include "./LocalFileProvider.h" +#include "./duration_utils.h" +#include "LocalFileProvider.h" + +#if defined(TCP_CORK) && !defined(TCP_NOPUSH) + #define TCP_NOPUSH TCP_CORK +#endif + +using namespace ts::server::file; +using namespace ts::server::file::transfer; + +inline void add_network_event(FileClient& transfer, event* ev, bool& ev_throttle_readd_flag, bool ignore_bandwidth) { + timeval tv{0, 1}, *ptv{nullptr}; + { + auto timeout = transfer.networking.disconnect_timeout; + if(timeout.time_since_epoch().count() > 0) { + auto now = std::chrono::system_clock::now(); + if(now < timeout) { + auto duration = timeout - now; + + auto seconds = std::chrono::duration_cast(timeout - now); + duration -= seconds; + + auto microseconds = std::chrono::duration_cast(timeout - now); + + tv.tv_sec = seconds.count(); + tv.tv_usec = microseconds.count(); + } + ptv = &tv; + } + } + + if(!ignore_bandwidth) { + if(ev_throttle_readd_flag) return; /* we're already throttled */ + + timeval ttv{}; + if(transfer.networking.throttle.should_throttle(ttv)) { + if(transfer.networking.event_throttle) + event_add(transfer.networking.event_throttle, &ttv); + ev_throttle_readd_flag = true; + return; + } + } + + event_add(ev, ptv); +} + +void FileClient::add_network_read_event(bool ignore_bandwidth) { + std::shared_lock slock{this->state_mutex}; + + switch (this->state) { + case STATE_DISCONNECTING: + case STATE_DISCONNECTED: + return; + + case STATE_AWAITING_KEY: + case STATE_TRANSFERRING: + break; + + default: + assert(false); + return; + } + if(this->state != STATE_AWAITING_KEY && this->state != STATE_TRANSFERRING) + return; + + add_network_event(*this, this->networking.event_read, this->networking.add_event_read, ignore_bandwidth); +} + +void FileClient::add_network_write_event(bool ignore_bandwidth) { + std::shared_lock slock{this->state_mutex}; + this->add_network_write_event_nolock(ignore_bandwidth); +} + +void FileClient::add_network_write_event_nolock(bool ignore_bandwidth) { + switch (this->state) { + case STATE_DISCONNECTED: + return; + + case STATE_DISCONNECTING: + if(this->transfer->direction == Transfer::DIRECTION_UPLOAD) + return; + /* flush our write buffer */ + break; + + case STATE_TRANSFERRING: + break; + + //case STATE_PENDING: + //case STATE_AWAITING_KEY: + default: + assert(false); + break; + } + + add_network_event(*this, this->networking.event_write, this->networking.add_event_write, ignore_bandwidth); +} + +bool FileClient::send_file_bytes(const void *snd_buffer, size_t size) { + if(this->networking.protocol == FileClient::PROTOCOL_TS_V1) { + 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->buffer.mutex}; + *this->buffer.buffer_tail = tbuffer; + this->buffer.buffer_tail = &tbuffer->next; + + buffer_size = (this->buffer.bytes += size); + } + + this->add_network_write_event(false); + return buffer_size > TRANSFER_MAX_CACHED_BYTES; + } else if(this->networking.protocol == FileClient::PROTOCOL_HTTPS) { + this->networking.pipe_ssl.send(pipes::buffer_view{snd_buffer, size}); + return this->buffer.bytes > TRANSFER_MAX_CACHED_BYTES; + } else { + return false; + } +} + +NetworkingStartResult LocalFileTransfer::start_networking() { + assert(!this->network.active); + + this->network.active = true; + this->network.event_base = event_base_new(); + if(!this->network.event_base) return NetworkingStartResult::OUT_OF_MEMORY; + + bool bound{false}; + for(auto& binding : this->network.bindings) { + binding->file_descriptor = socket(binding->address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0); + if(!binding->file_descriptor) { + logWarning(LOG_FT, "Failed to allocate socket for {}: {}/{}", binding->hostname, errno, strerror(errno)); + continue; + } + + + int enable = 1, disabled = 0; + + if (setsockopt(binding->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) + logWarning(LOG_FT, "Failed to activate SO_REUSEADDR for binding {} ({} | {})", binding->hostname, errno, strerror(errno)); + + if(setsockopt(binding->file_descriptor, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0) + logWarning(LOG_FT, "Failed to deactivate TCP_NOPUSH for binding {} ({} | {})", binding->hostname, errno, strerror(errno)); + + if(binding->address.ss_family == AF_INET6) { + if(setsockopt(binding->file_descriptor, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(int)) < 0) + logWarning(LOG_FT, "Failed to activate IPV6_V6ONLY for IPv6 binding {} ({} | {})", binding->hostname, errno, strerror(errno)); + } + if(fcntl(binding->file_descriptor, F_SETFD, FD_CLOEXEC) < 0) + logWarning(LOG_FT, "Failed to set flag FD_CLOEXEC for binding {} ({} | {})", binding->hostname, errno, strerror(errno)); + + + if (bind(binding->file_descriptor, (struct sockaddr *) &binding->address, sizeof(binding->address)) < 0) { + logError(LOG_FT, "Failed to bind server to {}. (Failed to bind socket: {} | {})", binding->hostname, errno, strerror(errno)); + goto reset_binding; + } + + if (listen(binding->file_descriptor, 8) < 0) { + logError(LOG_FT, "Failed to bind server to {}. (Failed to listen: {} | {})", binding->hostname, errno, strerror(errno)); + goto reset_binding; + } + + binding->handle = this; + binding->accept_event = event_new(this->network.event_base, binding->file_descriptor, (unsigned) EV_READ | (unsigned) EV_PERSIST, &LocalFileTransfer::callback_transfer_network_accept, &*binding); + if(!binding->accept_event) + goto reset_binding; + + event_add(binding->accept_event, nullptr); + logMessage(LOG_FT, "Started to listen on {}:{}", binding->hostname, net::port(binding->address)); + + bound = true; + continue; + + reset_binding: + if(binding->accept_event) { + event_free(binding->accept_event); + binding->accept_event = nullptr; + } + + if(binding->file_descriptor > 0) + ::close(binding->file_descriptor); + binding->file_descriptor = 0; + + binding->handle = nullptr; + } + if(!bound) { + event_base_free(std::exchange(this->network.event_base, nullptr)); + return NetworkingStartResult::NO_BINDINGS; + } + + this->network.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_network, this); + return NetworkingStartResult::SUCCESS; +} + +void LocalFileTransfer::shutdown_networking() { + if(!this->network.active) return; + this->network.active = false; + + for(auto& binding : this->network.bindings) { + if(binding->accept_event) { + event_del_block(binding->accept_event); + event_free(binding->accept_event); + binding->accept_event = nullptr; + } + + if(binding->file_descriptor > 0) + ::close(binding->file_descriptor); + binding->file_descriptor = 0; + + binding->handle = nullptr; + } + + { + std::unique_lock tlock{this->transfers_mutex}; + auto transfers = this->transfers_; + tlock.unlock(); + for(const auto& transfer : transfers) { + std::unique_lock slock{transfer->state_mutex}; + this->disconnect_client(transfer, slock, false); + } + } + + auto ev_base = std::exchange(this->network.event_base, nullptr); + if(ev_base) + event_base_loopbreak(ev_base); + + if(this->network.dispatch_thread.joinable()) + this->network.dispatch_thread.join(); + + if(ev_base) + event_base_free(ev_base); +} + +void LocalFileTransfer::dispatch_loop_network(void *provider_ptr) { + auto provider = reinterpret_cast(provider_ptr); + + while(provider->network.active) { + assert(provider->network.event_base); + event_base_loop(provider->network.event_base, EVLOOP_NO_EXIT_ON_EMPTY); + } +} + +NetworkInitializeResult LocalFileTransfer::initialize_networking(const std::shared_ptr &client, int file_descriptor) { + client->networking.file_descriptor = file_descriptor; + + client->networking.event_read = event_new(this->network.event_base, file_descriptor, EV_READ, &LocalFileTransfer::callback_transfer_network_read, &*client); + client->networking.event_write = event_new(this->network.event_base, file_descriptor, EV_WRITE, &LocalFileTransfer::callback_transfer_network_write, &*client); + client->networking.event_throttle = evtimer_new(this->network.event_base, &LocalFileTransfer::callback_transfer_network_throttle, &*client); + + if(!client->networking.event_read || !client->networking.event_write || !client->networking.event_throttle) + goto oom_exit; + + client->add_network_read_event(true); + + client->timings.connected = std::chrono::system_clock::now(); + client->timings.last_write = client->timings.connected; + client->timings.last_read = client->timings.connected; + + return NetworkInitializeResult::SUCCESS; + + oom_exit: + if(auto event{std::exchange(client->networking.event_read, nullptr)}; event) + event_free(event); + if(auto event{std::exchange(client->networking.event_write, nullptr)}; event) + event_free(event); + if(auto event{std::exchange(client->networking.event_throttle, nullptr)}; event) + event_free(event); + + return NetworkInitializeResult::OUT_OF_MEMORY; +} + +void LocalFileTransfer::finalize_networking(const std::shared_ptr &client, std::unique_lock& state_lock) { + assert(state_lock.owns_lock()); + + auto ev_read = std::exchange(client->networking.event_read, nullptr); + auto ev_write = std::exchange(client->networking.event_write, nullptr); + auto ev_throttle = std::exchange(client->networking.event_throttle, nullptr); + + state_lock.unlock(); + if (ev_read) { + event_del_block(ev_read); + event_free(ev_read); + } + if (ev_write) { + event_del_block(ev_write); + event_free(ev_write); + } + if (ev_throttle) { + event_del_block(ev_throttle); + event_free(ev_throttle); + } + state_lock.lock(); + + if (client->networking.file_descriptor > 0) { + ::shutdown(client->networking.file_descriptor, SHUT_RDWR); + ::close(client->networking.file_descriptor); + } + client->networking.file_descriptor = 0; +} + +void LocalFileTransfer::callback_transfer_network_accept(int fd, short, void *ptr_binding) { + auto binding = reinterpret_cast(ptr_binding); + auto transfer = binding->handle; + + sockaddr_storage address{}; + socklen_t address_length{sizeof(address)}; + auto client_fd = ::accept4(fd, reinterpret_cast(&address), &address_length, SOCK_NONBLOCK); + if(client_fd <= 0) { + /* TODO: Reserve one file descriptor in case of out of file descriptors (see current implementation) */ + logError(LOG_FT, "Failed to accept new client: {}/{}", errno, strerror(errno)); + return; + } + + auto client = std::make_shared(transfer); + memcpy(&client->networking.address, &address, sizeof(sockaddr_storage)); + + logMessage(LOG_FT, "{} Connection received.", client->log_prefix()); + auto ninit = transfer->initialize_networking(client, client_fd); + switch(ninit) { + case NetworkInitializeResult::SUCCESS: { + std::lock_guard tlock{transfer->transfers_mutex}; + transfer->transfers_.push_back(std::move(client)); + return; + } + case NetworkInitializeResult::OUT_OF_MEMORY: + client->state = FileClient::STATE_DISCONNECTED; /* required else the deallocate assert will fail */ + logError(LOG_FT, "{} Failed to initialize transfer client because we ran out of memory. Closing connection.", client->log_prefix()); + ::close(client_fd); + return; + + default: + client->state = FileClient::STATE_DISCONNECTED; /* required else the deallocate assert will fail */ + logError(LOG_FT, "{} Failed to initialize transfer client. Closing connection.", client->log_prefix()); + ::close(client_fd); + return; + } +} + +void LocalFileTransfer::callback_transfer_network_throttle(int, short, void *ptr_transfer) { + auto transfer = reinterpret_cast(ptr_transfer); + + if(std::exchange(transfer->networking.add_event_write, false)) + transfer->add_network_write_event(true); + + if(std::exchange(transfer->networking.add_event_read, false)) + transfer->add_network_read_event(true); +} + +void LocalFileTransfer::callback_transfer_network_read(int fd, short events, void *ptr_transfer) { + auto transfer = reinterpret_cast(ptr_transfer); + + if((unsigned) events & (unsigned) EV_TIMEOUT) { + /* should never happen, receive timeouts are done via the client tick */ + } + + if((unsigned) events & (unsigned) EV_READ) { + constexpr size_t buffer_size{4096}; + char buffer[buffer_size]; + + while(true) { + const auto max_read_buffer = transfer->networking.throttle.bytes_left(); + if(!max_read_buffer) break; /* network throttle */ + + auto read = ::recv(fd, buffer, std::min(buffer_size, std::min(max_read_buffer, transfer->networking.max_read_buffer_size)), MSG_NOSIGNAL | MSG_DONTWAIT); + //logTrace(0, "Read {}, max {} | {}", read, std::min(buffer_size, std::min(max_read_buffer, transfer->networking.max_read_buffer_size)), max_read_buffer); + if(read <= 0) { + if(read == 0) { + std::unique_lock slock{transfer->state_mutex}; + auto original_state = transfer->state; + transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false); + slock.unlock(); + + switch(original_state) { + case FileClient::STATE_AWAITING_KEY: + logMessage(LOG_FT, "{} Disconnected without sending any key or initializing a transfer.", transfer->log_prefix()); + break; + case FileClient::STATE_TRANSFERRING: { + assert(transfer->transfer); + + /* since we're counting the bytes directly on read, a disconnect would mean that not all data has been transferred */ + if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) { + logMessage(LOG_FT, "{} Disconnected while receiving file. Received {} out of {} bytes", + transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset); + } else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) { + logMessage(LOG_FT, "{} Disconnected while sending file. Send {} out of {} bytes", + transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset); + } + + transfer->handle->report_transfer_statistics(transfer->shared_from_this()); + if(auto callback{transfer->handle->callback_transfer_aborted}; callback) + callback(transfer->transfer, { TransferError::UNEXPECTED_CLIENT_DISCONNECT, "" }); + break; + } + default: + break; + } + } else { + if(errno == EAGAIN) { + transfer->add_network_read_event(false); + return; + } + + std::unique_lock slock{transfer->state_mutex}; + auto original_state = transfer->state; + transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false); + slock.unlock(); + + switch(original_state) { + case FileClient::STATE_AWAITING_KEY: + logMessage(LOG_FT, "{} Received a read error for an unauthenticated client: {}/{}. Closing connection.", transfer->log_prefix(), errno, strerror(errno)); + break; + case FileClient::STATE_TRANSFERRING: + assert(transfer->transfer); + + /* since we're counting the bytes directly on read, a disconnect would mean that not all data has been transferred */ + if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) { + logMessage(LOG_FT, "{} Received read error while receiving file. Received {} out of {} bytes: {}/{}", + transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset, errno, strerror(errno)); + } else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) { + logMessage(LOG_FT, "{} Received read error while sending file. Send {} out of {} bytes: {}/{}", + transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset, errno, strerror(errno)); + } + + transfer->handle->report_transfer_statistics(transfer->shared_from_this()); + if(auto callback{transfer->handle->callback_transfer_aborted}; callback) + callback(transfer->transfer, { TransferError::NETWORK_IO_ERROR, strerror(errno) }); + break; + default: + break; + } + return; + } + return; + } else { + transfer->timings.last_read = std::chrono::system_clock::now(); + transfer->statistics.network_bytes_received += read; + bool throttle_read = transfer->networking.throttle.increase_bytes(read); + + if(transfer->state != FileClient::STATE_AWAITING_KEY && !(transfer->state == FileClient::STATE_TRANSFERRING && transfer->transfer->direction == Transfer::DIRECTION_UPLOAD)) { + debugMessage(LOG_FT, "{} Received {} bytes without any need. Dropping them.", transfer->log_prefix(), read); + return; + } + + size_t bytes_buffered{0}; + if(transfer->state == FileClient::STATE_AWAITING_KEY) { + bytes_buffered = transfer->handle->handle_transfer_read(transfer->shared_from_this(), buffer, read); + } else if(transfer->state == FileClient::STATE_TRANSFERRING) { + if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) { + bytes_buffered = transfer->handle->handle_transfer_read(transfer->shared_from_this(), buffer, read); + } else { + debugMessage(LOG_FT, "{} Received {} bytes without any need. Dropping them.", transfer->log_prefix(), read); + } + } + + if(transfer->state == FileClient::STATE_DISCONNECTING || transfer->state == FileClient::STATE_DISCONNECTED) + break; + + if(bytes_buffered > TRANSFER_MAX_CACHED_BYTES) { + transfer->buffer.buffering_stopped = true; + debugMessage(LOG_FT, "{} Stopping network read, temp buffer full.", transfer->log_prefix()); + return; /* no read event readd, buffer filled */ + } + + if(throttle_read) + break; + } + } + + transfer->add_network_read_event(false); /* read event is not persistent */ + } +} + +void LocalFileTransfer::callback_transfer_network_write(int fd, short events, void *ptr_transfer) { + auto transfer = reinterpret_cast(ptr_transfer); + + if((unsigned) events & (unsigned) EV_TIMEOUT) { + if(transfer->state == FileClient::STATE_DISCONNECTING) { + { + std::unique_lock slock{transfer->state_mutex}; + transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false); + } + + logMessage(LOG_FT, "{} Flush timeout. Force closing connection.", transfer->log_prefix()); + return; + } + } + + if((unsigned) events & (unsigned) EV_WRITE) { + if(transfer->state != FileClient::STATE_DISCONNECTING && transfer->state != FileClient::STATE_TRANSFERRING) { + debugMessage(LOG_FT, "{} Tried to write data to send only stream. Dropping buffers.", transfer->log_prefix()); + + std::unique_lock block{transfer->buffer.mutex}; + auto head = std::exchange(transfer->buffer.buffer_head, nullptr); + transfer->buffer.buffer_tail = &transfer->buffer.buffer_head; + + while(head) { + auto next = head->next; + free_buffer(head); + head = next; + } + return; + } + + Buffer* buffer{nullptr}; + size_t buffer_left_size{0}; + + while(true) { + { + std::lock_guard block{transfer->buffer.mutex}; + buffer = transfer->buffer.buffer_head; + buffer_left_size = transfer->buffer.bytes; + } + if(!buffer) { + break; + } + + const auto max_write_bytes = transfer->networking.throttle.bytes_left(); + if(!max_write_bytes) break; /* network throttle */ + + assert(buffer->offset < buffer->length); + auto written = ::send(fd, buffer->data + buffer->offset, std::min(buffer->length - buffer->offset, max_write_bytes), MSG_DONTWAIT | MSG_NOSIGNAL); + if(written <= 0) { + if(written == 0) { + /* EOF, how the hell is this event possible?! (Read should already catch it) */ + logError(LOG_FT, "{} Client disconnected unexpectedly on write. Send {} bytes out of {}.", + transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset); + + transfer->handle->report_transfer_statistics(transfer->shared_from_this()); + if(auto callback{transfer->handle->callback_transfer_aborted}; callback) + callback(transfer->transfer, { TransferError::UNEXPECTED_CLIENT_DISCONNECT, strerror(errno) }); + + { + std::unique_lock slock{transfer->state_mutex}; + transfer->handle->disconnect_client(transfer->shared_from_this(), slock, true); + } + } else { + if(errno == EAGAIN) { + transfer->add_network_write_event(false); + break; + } + + logError(LOG_FT, "{} Received network write error. Send {} bytes out of {}. Closing transfer.", + transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset); + + transfer->handle->report_transfer_statistics(transfer->shared_from_this()); + if(auto callback{transfer->handle->callback_transfer_aborted}; callback) + callback(transfer->transfer, { TransferError::NETWORK_IO_ERROR, strerror(errno) }); + + { + std::unique_lock slock{transfer->state_mutex}; + transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false); + } + } + return; + } else { + buffer->offset += written; + assert(buffer->offset <= buffer->length); + if(buffer->length == buffer->offset) { + { + std::lock_guard block{transfer->buffer.mutex}; + transfer->buffer.buffer_head = buffer->next; + if(!buffer->next) + transfer->buffer.buffer_tail = &transfer->buffer.buffer_head; + assert(transfer->buffer.bytes >= written); + transfer->buffer.bytes -= written; + buffer_left_size = transfer->buffer.bytes; + } + + free_buffer(buffer); + } else { + std::lock_guard block{transfer->buffer.mutex}; + assert(transfer->buffer.bytes >= written); + transfer->buffer.bytes -= written; + buffer_left_size = transfer->buffer.bytes; + } + + transfer->timings.last_write = std::chrono::system_clock::now(); + transfer->statistics.network_bytes_send += written; + + if(transfer->networking.throttle.increase_bytes(written)) + break; /* we've to slow down */ + } + } + + if(buffer_left_size > 0) + transfer->add_network_write_event(false); + else if(transfer->state == FileClient::STATE_DISCONNECTING) { + if(transfer->statistics.file_bytes_transferred + transfer->transfer->file_offset == transfer->transfer->expected_file_size) { + logMessage(LOG_FT, "{} Finished file transfer within {}. Closing connection.", transfer->log_prefix(), duration_to_string(std::chrono::system_clock::now() - transfer->timings.key_received)); + transfer->handle->report_transfer_statistics(transfer->shared_from_this()); + if(auto callback{transfer->handle->callback_transfer_finished}; callback) + callback(transfer->transfer); + } else { + debugMessage(LOG_FT, "{} Flushed output buffer.", transfer->log_prefix()); + } + std::unique_lock slock{transfer->state_mutex}; + transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false); + return; + } + transfer->handle->enqueue_disk_io(transfer->shared_from_this()); + } +} + +inline std::string transfer_key_to_string(char key[TRANSFER_KEY_LENGTH]) { + std::string result{}; + result.resize(TRANSFER_KEY_LENGTH); + + for(size_t index{0}; index < TRANSFER_KEY_LENGTH; index++) + result[index] = isprint(key[index]) ? key[index] : '.'; + + return result; +} + +size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr &client, const char *buffer, size_t length) { + if(client->state == FileClient::STATE_AWAITING_KEY) { + if(client->networking.protocol == FileClient::PROTOCOL_UNKNOWN) { + /* lets read the key bytes (16) and then decide */ + + if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH) { + const auto bytes_write = std::min(TRANSFER_KEY_LENGTH - client->transfer_key.provided_bytes, length); + memcpy(client->transfer_key.key + client->transfer_key.provided_bytes, buffer, bytes_write); + length -= bytes_write; + buffer += bytes_write; + client->transfer_key.provided_bytes += bytes_write; + } + + if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH) + return 0; /* we need more data */ + + if(pipes::SSL::is_ssl((uint8_t*) client->transfer_key.key, client->transfer_key.provided_bytes)) { + client->networking.protocol = FileClient::PROTOCOL_HTTPS; + client->networking.max_read_buffer_size = (size_t) 1024 * 2; /* HTTPS-Header are sometimes a bit bigger. Dont cap max bandwidth here. */ + debugMessage(LOG_FT, "{} Using protocol HTTPS for file transfer.", client->log_prefix()); + + char first_bytes[TRANSFER_KEY_LENGTH]; + memcpy(first_bytes, client->transfer_key.key, TRANSFER_KEY_LENGTH); + client->transfer_key.provided_bytes = 0; + + this->handle_transfer_read(client, first_bytes, TRANSFER_KEY_LENGTH); + return this->handle_transfer_read(client, buffer, length); + } else { + client->networking.protocol = FileClient::PROTOCOL_TS_V1; + debugMessage(LOG_FT, "{} Using protocol RAWv1 for file transfer.", client->log_prefix()); + + return this->handle_transfer_read(client, buffer, length); + } + } else if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) { + auto key_result = this->handle_transfer_key_provided(client); + switch(key_result) { + case TransferKeyApplyResult::SUCCESS: + return length ? this->handle_transfer_read(client, buffer, length) : 0; + + case TransferKeyApplyResult::FILE_ERROR: + logMessage(LOG_FT, "{} Disconnecting client because we failed to open the target file.", client->log_prefix()); + return (size_t) -1; + + case TransferKeyApplyResult::UNKNOWN_KEY: + logMessage(LOG_FT, "{} Disconnecting client because we don't recognise his key ({}).", client->log_prefix(), transfer_key_to_string(client->transfer_key.key)); + return (size_t) -1; + + default: + logMessage(LOG_FT, "{} Disconnecting client because of an unknown key initialize error ({}).", client->log_prefix(), (int) key_result); + return (size_t) -1; + } + } else if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) { + /* TODO! */ + /* 1. Await HTTP header + * 2. Test for file transfer key & if its upload or download + */ + } else { + logError(LOG_FT, "{} Protocol variable contains unknown protocol. Disconnecting client.", client->log_prefix()); + + std::unique_lock slock{client->state_mutex}; + client->handle->disconnect_client(client->shared_from_this(), slock, true); + return (size_t) -1; + } + } else if(client->state == FileClient::STATE_TRANSFERRING) { + assert(client->transfer); + if(client->transfer->direction != Transfer::DIRECTION_UPLOAD) { + debugMessage(LOG_FT, "{} Read {} bytes from client even though we're only sending a file. Ignoring it.", client->log_prefix(), length); + return 0; + } + + if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) { + client->networking.pipe_ssl.process_incoming_data(pipes::buffer_view{buffer, length}); + return client->buffer.bytes; /* a bit unexact but the best we could get away with it */ + } else if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) { + client->statistics.file_bytes_transferred += length; + + bool transfer_finished{false}; + auto writte_offset = client->statistics.file_bytes_transferred + client->transfer->file_offset; + if(writte_offset > client->transfer->expected_file_size) { + logMessage(LOG_FT, "{} Client send {} too many bytes (Transfer length was {}). Dropping them, flushing the disk IO and closing the transfer.", client->log_prefix(), writte_offset - client->transfer->expected_file_size, duration_to_string(std::chrono::system_clock::now() - client->timings.key_received)); + length -= writte_offset - client->transfer->expected_file_size; + + transfer_finished = true; + } else if(writte_offset == client->transfer->expected_file_size) { + logMessage(LOG_FT, "{} File upload has been completed in {}. Flushing disk IO and closing the connection.", client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received)); + transfer_finished = true; + } + + auto tbuffer = allocate_buffer(length); + tbuffer->offset = 0; + tbuffer->length = length; + memcpy(tbuffer->data, buffer, length); + + size_t buffered_bytes; + { + std::lock_guard block{client->buffer.mutex}; + *client->buffer.buffer_tail = tbuffer; + client->buffer.buffer_tail = &tbuffer->next; + buffered_bytes = (client->buffer.bytes += length); + } + + this->enqueue_disk_io(client); + + if(transfer_finished) { + this->report_transfer_statistics(client); + if(auto callback{this->callback_transfer_finished}; callback) + callback(client->transfer); + + std::unique_lock slock{client->state_mutex}; + client->handle->disconnect_client(client->shared_from_this(), slock, true); + } + return buffered_bytes; + } else { + logWarning(LOG_FT, "{} Read message for client with unknown protocol. Dropping {} bytes.", client->log_prefix(), length); + return 0; + } + } else { + logWarning(LOG_FT, "{} Read message at invalid client state ({}). Dropping message.", client->log_prefix(), client->state); + } + return 0; +} + +TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std::shared_ptr &client) { + { + std::lock_guard tlock{this->transfers_mutex}; + for(auto it = this->pending_transfers.begin(); it != this->pending_transfers.end(); it++) { + if(memcmp((*it)->transfer_key, client->transfer_key.key, TRANSFER_KEY_LENGTH) == 0) { + client->transfer = *it; + this->pending_transfers.erase(it); + break; + } + } + } + + if(!client->transfer) { + logMessage(LOG_FT, "{} Received invalid transfer key. Disconnecting client.", client->log_prefix()); + + std::unique_lock slock{client->state_mutex}; + client->handle->disconnect_client(client->shared_from_this(), slock, false); + + return TransferKeyApplyResult::UNKNOWN_KEY; + } + + { + std::string absolute_path{}; + auto transfer = client->transfer; + switch (transfer->target_type) { + case Transfer::TARGET_TYPE_AVATAR: + absolute_path = this->file_system_.absolute_avatar_path(transfer->server_id, transfer->target_file_path); + logMessage(LOG_FT, "{} Initialized avatar transfer for avatar \"{}\" ({} bytes, transferring {} bytes).", + client->log_prefix(), transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset); + break; + case Transfer::TARGET_TYPE_ICON: + absolute_path = this->file_system_.absolute_icon_path(transfer->server_id, transfer->target_file_path); + logMessage(LOG_FT, "{} Initialized icon transfer for icon \"{}\" ({} bytes, transferring {} bytes).", + client->log_prefix(), transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset); + break; + case Transfer::TARGET_TYPE_CHANNEL_FILE: + absolute_path = this->file_system_.absolute_channel_path(transfer->server_id, transfer->channel_id, transfer->target_file_path); + logMessage(LOG_FT, "{} Initialized channel transfer for file \"{}/{}\" ({} bytes, transferring {} bytes).", + client->log_prefix(), transfer->channel_id, transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset); + break; + default: + logError(LOG_FT, "{} Tried to initialize client with unknown file target type ({}). Dropping transfer.", client->log_prefix(), (int) transfer->target_type); + this->report_transfer_statistics(client); + if(auto callback{this->callback_transfer_aborted}; callback) + callback(client->transfer, { TransferError::UNKNOWN, "invalid transfer type" }); + + { + std::unique_lock slock{client->state_mutex}; + client->handle->disconnect_client(client->shared_from_this(), slock, false); + } + return TransferKeyApplyResult::FILE_ERROR; + } + debugMessage(LOG_FT, "{} Absolute file path: {}", client->log_prefix(), absolute_path); + client->file.absolute_path = absolute_path; + } + + if(client->transfer->max_bandwidth > 0) { + debugMessage(LOG_FT, "{} Limit network bandwidth to {} bytes/second", client->log_prefix(), client->transfer->max_bandwidth); + client->networking.throttle.set_max_bandwidth(client->transfer->max_bandwidth); + } + client->networking.max_read_buffer_size = (size_t) -1; /* limit max bandwidth via throttle */ + + auto io_init_result = this->initialize_file_io(client); + if(io_init_result != FileInitializeResult::SUCCESS) { + logMessage(LOG_FT, "{} Failed to initialize file {}: {}/{}. Disconnecting client.", + client->log_prefix(), client->transfer->direction == Transfer::DIRECTION_UPLOAD ? "writer" : "reader", (int) io_init_result, kFileInitializeResultMessages[(int) io_init_result]); + + this->report_transfer_statistics(client); + if(auto callback{this->callback_transfer_aborted}; callback) + callback(client->transfer, { TransferError::DISK_INITIALIZE_ERROR, std::to_string((int) io_init_result) + "/" + std::string{kFileInitializeResultMessages[(int) io_init_result]} }); + + std::unique_lock slock{client->state_mutex}; + client->handle->disconnect_client(client->shared_from_this(), slock, false); + + return TransferKeyApplyResult::FILE_ERROR; + } + + { + std::unique_lock slock{client->state_mutex}; + if(client->state != FileClient::STATE_AWAITING_KEY) + return TransferKeyApplyResult::SUCCESS; /* something disconnected the client */ + + client->state = FileClient::STATE_TRANSFERRING; + } + + if(auto callback{this->callback_transfer_started}; callback) + callback(client->transfer); + + client->timings.key_received = std::chrono::system_clock::now(); + + if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) + this->enqueue_disk_io(client); /* we've to take initiative */ + + return TransferKeyApplyResult::SUCCESS; +} \ No newline at end of file diff --git a/file/local_server/duration_utils.h b/file/local_server/duration_utils.h new file mode 100644 index 0000000..743e92a --- /dev/null +++ b/file/local_server/duration_utils.h @@ -0,0 +1,35 @@ +#pragma once + +template +inline std::string duration_to_string(std::chrono::duration ms) { + std::string result{}; + + { + auto hours = std::chrono::duration_cast(ms); + if(hours.count()) + result += std::to_string(hours.count()) + " hours "; + ms -= hours; + } + { + auto minutes = std::chrono::duration_cast(ms); + if(minutes.count()) + result += std::to_string(minutes.count()) + " minutes "; + ms -= minutes; + } + { + auto seconds = std::chrono::duration_cast(ms); + if(seconds.count()) + result += std::to_string(seconds.count()) + " seconds "; + ms -= seconds; + } + if(result.empty()) { + auto milliseconds = std::chrono::duration_cast(ms); + if(milliseconds.count()) + result = std::to_string(milliseconds.count()) + " milliseconds "; + } + if(result.empty()) { + auto microseconds = std::chrono::duration_cast(ms); + result = std::to_string(microseconds.count()) + " microseconds "; + } + return result.substr(0, result.length() - 1); +} diff --git a/file/test/main.cpp b/file/test/main.cpp index f71d4c3..8025eec 100644 --- a/file/test/main.cpp +++ b/file/test/main.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace fs = std::experimental::filesystem; @@ -15,49 +16,67 @@ using namespace ts::server; struct Nothing {}; template -inline void print_response(const std::string& message, const std::shared_ptr, ResponseType>>& response) { +inline void print_fs_response(const std::string& message, const std::shared_ptr, ResponseType>>& response) { if(response->status == file::ExecuteStatus::ERROR) - logError(0, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message); + logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message); else if(response->status == file::ExecuteStatus::SUCCESS) - logMessage(0, "{}: success", message); + logMessage(LOG_FT, "{}: success", message); else - logWarning(0, "Unknown response state ({})!", (int) response->status); + logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status); +} + +template +inline void print_ft_response(const std::string& message, const std::shared_ptr>& response) { + if(response->status == file::ExecuteStatus::ERROR) + logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message); + else if(response->status == file::ExecuteStatus::SUCCESS) + logMessage(LOG_FT, "{}: success", message); + else + logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status); } inline void print_query(const std::string& message, const file::filesystem::AbstractProvider::directory_query_response_t& response) { if(response.status == file::ExecuteStatus::ERROR) - logError(0, "{}: {} => {}", message, (int) response.error().error_type, response.error().error_message); + logError(LOG_FT, "{}: {} => {}", message, (int) response.error().error_type, response.error().error_message); else if(response.status == file::ExecuteStatus::SUCCESS) { const auto& entries = response.response(); - logMessage(0, "{}: Found {} entries", message, entries.size()); + logMessage(LOG_FT, "{}: Found {} entries", message, entries.size()); for(auto& entry : entries) { if(entry.type == file::filesystem::DirectoryEntry::FILE) - logMessage(0, " - File {}", entry.name); + logMessage(LOG_FT, " - File {}", entry.name); else if(entry.type == file::filesystem::DirectoryEntry::DIRECTORY) - logMessage(0, " - Directory {}", entry.name); + logMessage(LOG_FT, " - Directory {}", entry.name); else - logMessage(0, " - Unknown {}", entry.name); - logMessage(0, " Write timestamp: {}", std::chrono::floor(entry.modified_at.time_since_epoch()).count()); - logMessage(0, " Size: {}", entry.size); + logMessage(LOG_FT, " - Unknown {}", entry.name); + logMessage(LOG_FT, " Write timestamp: {}", std::chrono::floor(entry.modified_at.time_since_epoch()).count()); + logMessage(LOG_FT, " Size: {}", entry.size); } } else - logWarning(0, "{}: Unknown response state ({})!", message, (int) response.status); + logWarning(LOG_FT, "{}: Unknown response state ({})!", message, (int) response.status); } int main() { + evthread_use_pthreads(); + + auto log_config = std::make_shared(); + log_config->terminalLevel = spdlog::level::trace; + logger::setup(log_config); + std::string error{}; if(!file::initialize(error)) { - logError(0, "Failed to initialize file server: {}", error); + logError(LOG_FT, "Failed to initialize file server: {}", error); return 0; } - logMessage(0, "File server started"); + logMessage(LOG_FT, "File server started"); auto instance = file::server(); - auto& fs = instance->file_system(); + +#if 0 + auto& fs = instance->file_system(); { auto response = fs.initialize_server(0); response->wait(); - print_response("Server init result", response); + print_fs_response("Server init result", response); if(response->status != file::ExecuteStatus::SUCCESS) return 0; } @@ -66,28 +85,28 @@ int main() { { auto response = fs.create_channel_directory(0, 2, "/"); response->wait(); - print_response("Channel dir create A", response); + print_fs_response("Channel dir create A", response); } { auto response = fs.create_channel_directory(0, 2, "/test-folder/"); response->wait(); - print_response("Channel dir create B", response); + print_fs_response("Channel dir create B", response); } { auto response = fs.create_channel_directory(0, 2, "../test-folder/"); response->wait(); - print_response("Channel dir create C", response); + print_fs_response("Channel dir create C", response); } { auto response = fs.create_channel_directory(0, 2, "./test-folder/../test-folder-2"); response->wait(); - print_response("Channel dir create D", response); + print_fs_response("Channel dir create D", response); } { @@ -111,15 +130,52 @@ int main() { { auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-2", "./test-folder/../test-folder-3"); response->wait(); - print_response("Folder rename A", response); + print_fs_response("Folder rename A", response); } { auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-3", "./test-folder/../test-folder-2"); response->wait(); - print_response("Folder rename B", response); + print_fs_response("Folder rename B", response); } +#endif +#if 1 + auto& ft = instance->file_transfer(); + + ft.callback_transfer_finished = [](const std::shared_ptr& transfer) { + logMessage(0, "Transfer finished"); + }; + + ft.callback_transfer_started = [](const std::shared_ptr& transfer) { + logMessage(0, "Transfer started"); + }; + + ft.callback_transfer_aborted = [](const std::shared_ptr& transfer, const file::transfer::TransferError& error) { + logMessage(0, "Transfer aborted ({}/{})", (int) error.error_type, error.error_message); + }; + + ft.callback_transfer_statistics = [](const std::shared_ptr& transfer, const file::transfer::TransferStatistics& stats) { + logMessage(0, "Transfer stats. New file bytes: {}, delta bytes send {}", stats.delta_file_bytes_transferred, stats.delta_network_bytes_send); + }; + + { + auto response = ft.initialize_channel_transfer(file::transfer::Transfer::DIRECTION_DOWNLOAD, 0, 2, { + "test2.txt", + false, + 4, + 120, + 16 + }); + response->wait(); + print_ft_response("Download test.txt", response); + if(response->succeeded()) + logMessage(LOG_FT, "Download key: {}", std::string{response->response()->transfer_key, TRANSFER_KEY_LENGTH}); + } +#endif + + std::this_thread::sleep_for(std::chrono::seconds{120}); //TODO: Test file locking + file::finalize(); return 0; } \ No newline at end of file diff --git a/git-teaspeak b/git-teaspeak index ac7d6a0..7261d53 160000 --- a/git-teaspeak +++ b/git-teaspeak @@ -1 +1 @@ -Subproject commit ac7d6a0a1ee75aa6a9e77382c9a010f3827327ba +Subproject commit 7261d531f3c10be1437a4572369260aeff075e6c diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 1b58393..0a01a9a 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -62,6 +62,7 @@ set(SERVER_SOURCE_FILES src/client/command_handler/client.cpp src/client/command_handler/server.cpp src/client/command_handler/misc.cpp + src/client/command_handler/bulk_parsers.cpp src/client/ConnectedClientNotifyHandler.cpp src/VirtualServerManager.cpp diff --git a/server/src/ConnectionStatistics.h b/server/src/ConnectionStatistics.h index c627d57..00ebcbf 100644 --- a/server/src/ConnectionStatistics.h +++ b/server/src/ConnectionStatistics.h @@ -142,7 +142,7 @@ namespace ts { void tick(); - [[nodiscard]] inline const BandwidthEntry& total_stats() const { return this->total_statistics; } + [[nodiscard]] inline const BandwidthEntry& total_stats() const { return this->total_statistics; } [[nodiscard]] inline BandwidthEntry second_stats() const { return this->statistics_second; } [[nodiscard]] BandwidthEntry minute_stats() const; @@ -151,7 +151,7 @@ namespace ts { private: std::shared_ptr handle; - BandwidthEntry total_statistics{}; + BandwidthEntry total_statistics{}; BandwidthEntry> statistics_second_current{}; BandwidthEntry statistics_second{}; /* will be updated every second by the stats from the "current_second" */ diff --git a/server/src/VirtualServer.cpp b/server/src/VirtualServer.cpp index 8b13952..920f73d 100644 --- a/server/src/VirtualServer.cpp +++ b/server/src/VirtualServer.cpp @@ -1216,4 +1216,41 @@ void VirtualServer::send_text_message(const std::shared_ptr &chann auto conversation = conversations->get_or_create(channel->channelId()); conversation->register_message(sender->getClientDatabaseId(), sender->getUid(), sender->getDisplayName(), now, message); } +} + +void VirtualServer::update_channel_from_permissions(const std::shared_ptr &channel, const std::shared_ptr& issuer) { + bool require_view_update; + auto property_updates = channel->update_properties_from_permissions(require_view_update); + + if(!property_updates.empty()) { + this->forEachClient([&](const std::shared_ptr& cl) { + shared_lock client_channel_lock(cl->channel_lock); + cl->notifyChannelEdited(channel, property_updates, issuer, false); + }); + } + + if(require_view_update) { + auto l_source = this->channelTree->findLinkedChannel(channel->channelId()); + this->forEachClient([&](const shared_ptr& cl) { + /* server tree read lock still active */ + auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId()); + sassert(l_source); + if(cl->currentChannel) sassert(l_target); + + { + unique_lock client_channel_lock(cl->channel_lock); + + deque deleted; + for(const auto& [flag_visible, channel] : cl->channels->update_channel(l_source, l_target)) { + if(flag_visible) { + cl->notifyChannelShow(channel->channel(), channel->previous_channel); + } else { + deleted.push_back(channel->channelId()); + } + } + if(!deleted.empty()) + cl->notifyChannelHide(deleted, false); + } + }); + } } \ No newline at end of file diff --git a/server/src/VirtualServer.h b/server/src/VirtualServer.h index 505417f..eb31e5c 100644 --- a/server/src/VirtualServer.h +++ b/server/src/VirtualServer.h @@ -278,6 +278,9 @@ namespace ts { inline int voice_encryption_mode() { return this->_voice_encryption_mode; } inline std::shared_ptr conversation_manager() { return this->_conversation_manager; } + + + void update_channel_from_permissions(const std::shared_ptr& /* channel */, const std::shared_ptr& /* issuer */); protected: bool registerClient(std::shared_ptr); bool unregisterClient(std::shared_ptr, std::string, std::unique_lock& channel_tree_lock); diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 84759aa..d22947f 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -605,7 +605,7 @@ bool ConnectedClient::notifyError(const command_result& result, const std::strin auto bulks = result.bulks(); command.reserve_bulks(bulks->size()); for(size_t index{0}; index < bulks->size(); index++) { - auto entry = bulks->at(index); + auto& entry = bulks->at(index); switch (entry.type()) { case command_result_type::error: write_command_result_error(command.bulk(index), entry); @@ -828,14 +828,14 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) { command_result result; try { - result = this->handleCommand(cmd); + result.reset(this->handleCommand(cmd)); } catch(invalid_argument& ex){ logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what()); if(disconnectOnFail) { this->disconnect("Invalid argument (" + string(ex.what()) + ")"); return false; } else { - result = command_result{error::parameter_convert, ex.what()}; + result.reset(command_result{error::parameter_convert, ex.what()}); } } catch (exception& ex) { logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received exception with message: {}", CLIENT_STR_LOG_PREFIX, ex.what()); @@ -843,7 +843,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) { this->disconnect("Error while command handling (" + string(ex.what()) + ")!"); return false; } else { - result = command_result{error::vs_critical}; + result.reset(command_result{error::vs_critical}); } } catch (...) { this->disconnect("Error while command handling! (unknown)"); diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index 3eaf590..c4a38a6 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -867,11 +867,11 @@ command_result SpeakingClient::handleCommand(Command &command) { if(this->handshake.state == HandshakeState::BEGIN || this->handshake.state == HandshakeState::IDENTITY_PROOF) { command_result result; if(command.command() == "handshakebegin") - result = this->handleCommandHandshakeBegin(command); + result.reset(this->handleCommandHandshakeBegin(command)); else if(command.command() == "handshakeindentityproof") - result = this->handleCommandHandshakeIdentityProof(command); + result.reset(this->handleCommandHandshakeIdentityProof(command)); else - result = command_result{error::client_not_logged_in}; + result.reset(command_result{error::client_not_logged_in}); if(result.has_error()) this->postCommandHandler.push_back([&]{ diff --git a/server/src/client/command_handler/bulk_parsers.cpp b/server/src/client/command_handler/bulk_parsers.cpp new file mode 100644 index 0000000..485893a --- /dev/null +++ b/server/src/client/command_handler/bulk_parsers.cpp @@ -0,0 +1,5 @@ +// +// Created by WolverinDEV on 07/05/2020. +// + +#include "bulk_parsers.h" diff --git a/server/src/client/command_handler/bulk_parsers.h b/server/src/client/command_handler/bulk_parsers.h new file mode 100644 index 0000000..d4ac059 --- /dev/null +++ b/server/src/client/command_handler/bulk_parsers.h @@ -0,0 +1,240 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "./helpers.h" + +namespace ts::command::bulk_parser { + template + class PermissionBulkParser { + public: + explicit PermissionBulkParser(ts::ParameterBulk& bulk) { + if(bulk.has("permid")) { + auto type = bulk["permid"].as(); + if ((type & PERM_ID_GRANT) != 0) { + type &= ~PERM_ID_GRANT; + } + + this->permission_ = permission::resolvePermissionData(type); + } else if(bulk.has("permsid")) { + auto permission_name = bulk["permsid"].string(); + this->permission_ = permission::resolvePermissionData(permission_name); + this->grant_ = this->permission_->grantName() == permission_name;; + } else { + this->error_.reset(ts::command_result{error::parameter_missing, "permid"}); + return; + } + + if(this->permission_->is_invalid()) { + this->error_.reset(ts::command_result{error::parameter_invalid}); + return; + } + + if(kParseValue) { + if(!bulk.has("permvalue")) { + this->error_.reset(ts::command_result{error::parameter_missing, "permvalue"}); + return; + } + + this->value_ = bulk["permvalue"].as(); + this->flag_skip_ = bulk.has("permskip") && bulk["permskip"].as(); + this->flag_negated_ = bulk.has("permnegated") && bulk["permnegated"].as(); + } + } + PermissionBulkParser(const PermissionBulkParser&) = delete; + PermissionBulkParser(PermissionBulkParser&&) = default; + + ~PermissionBulkParser() { + this->error_.release_data(); + } + + [[nodiscard]] inline bool has_error() const { + assert(!this->error_released_); + return this->error_.has_error(); + } + + [[nodiscard]] inline bool has_value() const { return this->value_ != permission::undefined; } + [[nodiscard]] inline bool is_grant_permission() const { return this->grant_; } + + [[nodiscard]] inline const auto& permission() const { return this->permission_; } + [[nodiscard]] inline auto permission_type() const { return this->permission_->type; } + + [[nodiscard]] inline auto value() const { return this->value_; } + [[nodiscard]] inline auto flag_skip() const { return this->flag_skip_; } + [[nodiscard]] inline auto flag_negated() const { return this->flag_negated_; } + + [[nodiscard]] inline ts::command_result release_error() { + assert(!std::exchange(this->error_released_, true)); + return std::move(this->error_); + } + + inline void emplace_custom_error(ts::command_result&& result) { + assert(!this->error_released_); + this->error_.reset(std::forward(result)); + } + + inline void apply_to(const std::shared_ptr& manager, permission::v2::PermissionUpdateType mode) const { + if(this->is_grant_permission()) { + manager->set_permission(this->permission_type(), permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, mode); + } else { + manager->set_permission( + this->permission_type(), + { this->value(), true }, + mode, + permission::v2::PermissionUpdateType::do_nothing + ); + } + } + + inline void apply_to_channel(const std::shared_ptr& manager, permission::v2::PermissionUpdateType mode, ChannelId channel_id) const { + if(this->is_grant_permission()) { + manager->set_channel_permission(this->permission_type(), channel_id, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, mode); + } else { + manager->set_channel_permission( + this->permission_type(), + channel_id, + { this->value(), true }, + mode, + permission::v2::PermissionUpdateType::do_nothing + ); + } + } + + [[nodiscard]] inline bool is_group_property() const { + return permission_is_group_property(this->permission_type()); + } + + [[nodiscard]] inline bool is_client_view_property() const { + return permission_is_client_property(this->permission_type()); + } + private: + std::shared_ptr permission_{nullptr}; + bool grant_{false}; + + bool flag_skip_{false}; + bool flag_negated_{false}; + + permission::PermissionValue value_{0}; + ts::command_result error_{error::ok}; + +#ifndef NDEBUG + bool error_released_{false}; +#endif + }; + + template + class PermissionBulksParser { + public: + PermissionBulksParser(const PermissionBulksParser&) = delete; + PermissionBulksParser(PermissionBulksParser&&) = default; + + template + struct FilteredPermissionIterator : public base_iterator { + public: + FilteredPermissionIterator() = default; + explicit FilteredPermissionIterator(base_iterator position, base_iterator end = {}) : base_iterator{position}, end_{end} { + if(*this != this->end_) { + const auto& entry = **this; + if(entry.has_error()) + this->operator++(); + } + } + + FilteredPermissionIterator& operator++() { + while(true) { + base_iterator::operator++(); + if(*this == this->end_) break; + + const auto& entry = **this; + if(!entry.has_error()) break; + } + return *this; + } + + [[nodiscard]] FilteredPermissionIterator operator++(int) const { + FilteredPermissionIterator copy = *this; + ++*this; + return copy; + } + + private: + base_iterator end_; + }; + + struct FilteredPermissionListIterable { + typedef typename std::vector>::const_iterator const_iterator; + public: + FilteredPermissionListIterable(const_iterator begin, const_iterator end) noexcept : begin_{begin}, end_{end} {} + + FilteredPermissionIterator begin() const { + return FilteredPermissionIterator{this->begin_, this->end_}; + } + + FilteredPermissionIterator end() const { + return FilteredPermissionIterator{this->end_, this->end_}; + } + private: + const_iterator begin_; + const_iterator end_; + }; + + explicit PermissionBulksParser(ts::Command& command) { + this->permissions_.reserve(command.bulkCount()); + for(size_t index{0}; index < command.bulkCount(); index++) + this->permissions_.emplace_back(command[index]); + } + + [[nodiscard]] inline bool validate(const std::shared_ptr& issuer, ChannelId channel_id) { + auto ignore_granted_values = permission::v2::permission_granted(1, issuer->calculate_permission(permission::b_permission_modify_power_ignore, channel_id)); + if(!ignore_granted_values) { + auto max_value = issuer->calculate_permission(permission::i_permission_modify_power, channel_id, false); + if(!max_value.has_value) { + for(PermissionBulkParser& permission : this->permissions_) { + if(permission.has_error()) continue; + + permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power}); + } + return false; + } + + for(size_t index{0}; index < this->permissions_.size(); index++) { + PermissionBulkParser& permission = this->permissions_[index]; + if(permission.has_error()) continue; + + if(kParseValue && permission_require_granted_value(permission.permission_type()) && !permission::v2::permission_granted(permission.value(), max_value)) { + permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power}); + continue; + } + + if(!permission::v2::permission_granted(1, issuer->calculate_permission(permission.permission_type(), channel_id, true))) { + permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power}); + continue; + } + } + } + return true; + } + + [[nodiscard]] inline FilteredPermissionListIterable iterate_valid_permissions() const { + return FilteredPermissionListIterable{this->permissions_.begin(), this->permissions_.end()}; + } + + [[nodiscard]] inline ts::command_result build_command_result() { + assert(!std::exchange(this->result_created_, true)); + ts::command_result_bulk result{}; + + for(auto& permission : this->permissions_) + result.insert_result(std::forward(permission.release_error())); + + return ts::command_result{std::move(result)}; + } + private: + std::vector> permissions_{}; +#ifndef NDEBUG + bool result_created_{false}; +#endif + }; +} \ No newline at end of file diff --git a/server/src/client/command_handler/channel.cpp b/server/src/client/command_handler/channel.cpp index 7682574..3c3c89f 100644 --- a/server/src/client/command_handler/channel.cpp +++ b/server/src/client/command_handler/channel.cpp @@ -20,6 +20,7 @@ #include #include "helpers.h" +#include "./bulk_parsers.h" #include #include @@ -407,46 +408,16 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); - auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, false); - if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); - - auto conOnError = cmd[0].has("continueonerror"); - bool updateList = false; - - auto permission_manager = channelGroup->permissions(); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); - - auto val = cmd[index]["permvalue"].as(); - if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if(!permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); - } else { - permission_manager->set_permission( - permType, - {cmd[index]["permvalue"], 0}, - permission::v2::PermissionUpdateType::set_value, - permission::v2::PermissionUpdateType::do_nothing, - - cmd[index]["permskip"].as() ? 1 : 0, - cmd[index]["permnegated"].as() ? 1 : 0 - ); - updateList |= permission_is_group_property(permType); - } + bool updateList{false}; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::set_value); + updateList |= ppermission.is_group_property(); } - if(updateList) channelGroup->apply_properties_from_permissions(); @@ -465,7 +436,8 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { } }); } - return command_result{error::ok}; + + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { @@ -475,29 +447,14 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); - bool updateList = false; - bool conOnError = cmd[0].has("continueonerror"); + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - auto permission_manager = channelGroup->permissions(); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd) - - if(!permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); - } else { - permission_manager->set_permission( - permType, - permission::v2::empty_permission_values, - permission::v2::PermissionUpdateType::delete_value, - permission::v2::PermissionUpdateType::do_nothing - ); - updateList |= permission_is_group_property(permType); - } + bool updateList{false}; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::delete_value); + updateList |= ppermission.is_group_property(); } if(updateList) @@ -518,7 +475,8 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { } }); } - return command_result{error::ok}; + + return pparser.build_command_result(); } //TODO: Test if parent or previous is deleted! @@ -1479,101 +1437,33 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) { ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true); - auto max_value = this->calculate_permission(permission::i_permission_modify_power, channel_id, false); - if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; - - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id)); - auto updateClients = false, update_view = false, update_channel_properties = false; - - bool conOnError = cmd[0].has("continueonerror"); + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), channel->channelId())) + return pparser.build_command_result(); auto permission_manager = channel->permissions(); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); + auto updateClients = false, update_join_permissions = false, update_channel_properties = false; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::set_value); - auto val = cmd[index]["permvalue"].as(); - if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if(!permission::v2::permission_granted(1, this->calculate_permission(permType, channel_id, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - - if (grant) { - permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); - } else { - permission_manager->set_permission( - permType, - {cmd[index]["permvalue"], 0}, - permission::v2::PermissionUpdateType::set_value, - permission::v2::PermissionUpdateType::do_nothing, - - cmd[index]["permskip"].as() ? 1 : 0, - cmd[index]["permnegated"].as() ? 1 : 0 - ); - updateClients |= permission_is_client_property(permType); - update_view |= permType == permission::i_channel_needed_view_power; - update_channel_properties |= channel->permission_require_property_update(permType); - - if (permType == permission::i_icon_id) { - if(this->server) { - auto self_ref = this->ref(); - this->server->forEachClient([&](std::shared_ptr cl) { - shared_lock client_channel_lock(cl->channel_lock); - cl->notifyChannelEdited(channel, {property::CHANNEL_ICON_ID}, self_ref, false); - }); - } - continue; - } - } + updateClients |= ppermission.is_client_view_property(); + update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power; + update_channel_properties |= channel->permission_require_property_update(ppermission.permission_type()); } - /* broadcast the updated channel properties */ - if(update_channel_properties) { - auto updates = channel->update_properties_from_permissions(); - if(!updates.empty() && this->server){ - auto self_ref = this->ref(); - this->server->forEachClient([&](std::shared_ptr cl) { - shared_lock client_channel_lock(cl->channel_lock); - cl->notifyChannelEdited(channel, updates, self_ref, false); - }); - } - } + if(update_channel_properties && this->server) + this->server->update_channel_from_permissions(channel, this->ref()); - if(updateClients && this->server) - for(const auto& client : this->server->getClientsByChannel(channel)) { - /* let them lock the server channel tree as well (read lock so does not matter) */ - client->updateChannelClientProperties(true, true); - client->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ - } - - if(update_view && this->server) { - auto l_source = this->server->channelTree->findLinkedChannel(channel->channelId()); - this->server->forEachClient([&](const shared_ptr& cl) { - /* server tree read lock still active */ - auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId()); - sassert(l_source); - if(cl->currentChannel) sassert(l_target); - - { - unique_lock client_channel_lock(cl->channel_lock); - - deque deleted; - for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) { - if(update_entry.first) - cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); - else deleted.push_back(update_entry.second->channelId()); - } - if(!deleted.empty()) - cl->notifyChannelHide(deleted, false); - } + if((updateClients || update_join_permissions) && this->server) { + this->server->forEachClient([&](std::shared_ptr cl) { + if(updateClients && cl->currentChannel == channel) + cl->updateChannelClientProperties(true, true); + if(update_join_permissions) + cl->join_state_id++; }); } - return command_result{error::ok};; + + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) { @@ -1585,71 +1475,33 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) { ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true); - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id)); - bool conOnError = cmd[0].has("continueonerror"); - auto updateClients = false, update_view = false, update_channel_properties = false; + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), channel->channelId())) + return pparser.build_command_result(); auto permission_manager = channel->permissions(); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); + auto updateClients = false, update_join_permissions = false, update_channel_properties = false; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::delete_value); - if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, channel_id, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); - } else { - permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing); - updateClients |= permission_is_client_property(permType); - update_view |= permType == permission::i_channel_needed_view_power; - update_channel_properties |= channel->permission_require_property_update(permType); - } + updateClients |= ppermission.is_client_view_property(); + update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power; + update_channel_properties |= channel->permission_require_property_update(ppermission.permission_type()); } - /* broadcast the updated channel properties */ - if(update_channel_properties) { - auto updates = channel->update_properties_from_permissions(); - if(!updates.empty() && this->server){ - auto self_ref = this->ref(); - this->server->forEachClient([&](std::shared_ptr cl) { - shared_lock client_channel_lock(cl->channel_lock); - cl->notifyChannelEdited(channel, updates, self_ref, false); - }); - } - } + if(update_channel_properties && this->server) + this->server->update_channel_from_permissions(channel, this->ref()); - if(updateClients && this->server) + if((updateClients || update_join_permissions) && this->server) { this->server->forEachClient([&](std::shared_ptr cl) { - if(cl->currentChannel == channel) { + if(updateClients && cl->currentChannel == channel) cl->updateChannelClientProperties(true, true); - cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ - } - }); - if(update_view && this->server) { - this->server->forEachClient([&](std::shared_ptr cl) { - /* server tree read lock still active */ - auto l_source = cl->server->channelTree->findLinkedChannel(channel->channelId()); - auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId()); - sassert(l_source); - if(cl->currentChannel) sassert(l_target); - - { - unique_lock client_channel_lock(cl->channel_lock); - - deque deleted; - for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) { - if(update_entry.first) - cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); - else deleted.push_back(update_entry.second->channelId()); - } - if(!deleted.empty()) - cl->notifyChannelHide(deleted, false); - } + if(update_join_permissions) + cl->join_state_id++; }); } - return command_result{error::ok}; + + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandChannelClientPermList(Command &cmd) { @@ -1728,27 +1580,21 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id); } - bool conOnError = cmd[0].has("continueonerror"), update_view = false; - auto cll = this->server->findClientsByCldbId(cldbid); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), channel->channelId())) + return pparser.build_command_result(); - if(!permission::v2::permission_granted(0, this->calculate_permission(permType, channel_id, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value); - } else { - mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing); - update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power; - } + bool update_view{false}; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::delete_value, channel->channelId()); + update_view |= ppermission.is_client_view_property(); } serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); - if (!cll.empty()) { - for (const auto &elm : cll) { + + auto onlineClients = this->server->findClientsByCldbId(cldbid); + if (!onlineClients.empty()) { + for (const auto &elm : onlineClients) { if(elm->update_cached_permissions()) /* update cached calculated permissions */ elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ @@ -1774,7 +1620,7 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) } } - return command_result{error::ok}; + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) { @@ -1791,45 +1637,25 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) if(!channel) return command_result{error::vs_critical}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); - { - auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id); - ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id); - } + auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id); + ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id); - auto max_value = this->calculate_permission(permission::i_permission_modify_power, channel_id, false); - if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), channel->channelId())) + return pparser.build_command_result(); - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id)); - auto update_view = false; - - bool conOnError = cmd[0].has("continueonerror"); - auto onlineClientInstances = this->server->findClientsByCldbId(cldbid); - - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); - - auto val = cmd[index]["permvalue"].as(); - if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if(!permission::v2::permission_granted(1, this->calculate_permission(permType, channel_id, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - mgr->set_channel_permission(permType, channel->channelId(), {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); - } else { - mgr->set_channel_permission(permType, channel->channelId(), {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0); - update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power; - } + bool update_view{false}; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::set_value, channel->channelId()); + update_view |= ppermission.is_client_view_property(); } serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); - if (!onlineClientInstances.empty()) - for (const auto &elm : onlineClientInstances) { + + + auto onlineClients = this->server->findClientsByCldbId(cldbid); + if (!onlineClients.empty()) + for (const auto &elm : onlineClients) { if (elm->update_cached_permissions()) /* update cached calculated permissions */ elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ @@ -1853,7 +1679,7 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } - return command_result{error::ok}; + return pparser.build_command_result(); } diff --git a/server/src/client/command_handler/client.cpp b/server/src/client/command_handler/client.cpp index aca7cec..1881c28 100644 --- a/server/src/client/command_handler/client.cpp +++ b/server/src/client/command_handler/client.cpp @@ -20,6 +20,7 @@ #include #include "helpers.h" +#include "./bulk_parsers.h" #include #include @@ -241,6 +242,8 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { for(auto& client : clients) { auto oldChannel = client->getChannel(); + if(!oldChannel) continue; + this->server->client_move( client.client, channel, @@ -964,33 +967,17 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) { auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0)); - auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, false); - if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); - bool conOnError = cmd[0].has("continueonerror"); - auto update_channels = false; - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - auto val = cmd[index]["permvalue"].as(); - if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if(!permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - mgr->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); - } else { - mgr->set_permission(permType, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0); - update_channels |= permission_is_client_property(permType); - } + bool update_channels{false}; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::set_value); + update_channels |= ppermission.is_client_view_property(); } + serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); auto onlineClients = this->server->findClientsByCldbId(cldbid); if (!onlineClients.empty()) @@ -1002,7 +989,7 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) { elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } - return command_result{error::ok}; + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) { @@ -1016,36 +1003,28 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) { auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0)); - bool conOnError = cmd[0].has("continueonerror"); - auto onlineClients = this->server->findClientsByCldbId(cmd["cldbid"]); - auto update_channel = false; - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd) + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - if(!permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - - if (grant) { - mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value); - } else { - mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing); - update_channel |= permission_is_client_property(permType); - } + bool update_channels{false}; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::delete_value); + update_channels |= ppermission.is_client_view_property(); } serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); + auto onlineClients = this->server->findClientsByCldbId(cldbid); if (!onlineClients.empty()) for (const auto &elm : onlineClients) { if(elm->update_cached_permissions()) /* update cached calculated permissions */ elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - if(update_channel) + if(update_channels) elm->updateChannelClientProperties(true, true); elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } - return command_result{error::ok}; + + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandClientPermList(Command &cmd) { diff --git a/server/src/client/command_handler/music.cpp b/server/src/client/command_handler/music.cpp index 18ebf07..231b876 100644 --- a/server/src/client/command_handler/music.cpp +++ b/server/src/client/command_handler/music.cpp @@ -30,6 +30,7 @@ #include #include "helpers.h" +#include "./bulk_parsers.h" #include #include @@ -558,35 +559,14 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) { if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) return command_result{perr}; - auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, true); - if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); + for(const auto& ppermission : pparser.iterate_valid_permissions()) + ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value); - - bool conOnError = cmd[0].has("continueonerror"); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); - - auto val = cmd[index]["permvalue"].as(); - if(permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if(!ignore_granted_values && !permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - playlist->permission_manager()->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); - } else { - playlist->permission_manager()->set_permission(permType, {cmd[index]["permvalue"],0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"].as(), cmd[index]["permnegated"].as()); - } - } - - return command_result{error::ok}; + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) { @@ -600,26 +580,14 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) { if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) return command_result{perr}; - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); - bool conOnError = cmd[0].has("continueonerror"); + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); + for(const auto& ppermission : pparser.iterate_valid_permissions()) + ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value); - if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - - if (grant) { - playlist->permission_manager()->set_permission(permType, {0, 0}, permission::v2::do_nothing, permission::v2::delete_value); - } else { - playlist->permission_manager()->set_permission(permType, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing); - } - } - - return command_result{error::ok}; + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandPlaylistClientList(ts::Command &cmd) { @@ -735,34 +703,14 @@ command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command & if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) return command_result{perr}; - auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, true); - if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), this->getClientDatabaseId())) + return pparser.build_command_result(); - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); + for(const auto& ppermission : pparser.iterate_valid_permissions()) + ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value, client_id); - bool conOnError = cmd[0].has("continueonerror"); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); - - auto val = cmd[index]["permvalue"].as(); - if(permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if(!ignore_granted_values && !permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - playlist->permission_manager()->set_channel_permission(permType, client_id, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); - } else { - playlist->permission_manager()->set_channel_permission(permType, client_id, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"].as(), cmd[index]["permnegated"].as()); - } - } - - return command_result{error::ok}; + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &cmd) { @@ -779,26 +727,15 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command & if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) return command_result{perr}; - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); - bool conOnError = cmd[0].has("continueonerror"); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), this->getClientDatabaseId())) + return pparser.build_command_result(); - if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } + for(const auto& ppermission : pparser.iterate_valid_permissions()) + ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value, client_id); - - if (grant) { - playlist->permission_manager()->set_channel_permission(permType, client_id, {0, 0}, permission::v2::do_nothing, permission::v2::delete_value); - } else { - playlist->permission_manager()->set_channel_permission(permType, client_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing); - } - } - - return command_result{error::ok}; + return pparser.build_command_result(); } constexpr auto max_song_meta_info = 1024 * 512; diff --git a/server/src/client/command_handler/server.cpp b/server/src/client/command_handler/server.cpp index 4a57dfc..7401b05 100644 --- a/server/src/client/command_handler/server.cpp +++ b/server/src/client/command_handler/server.cpp @@ -4,9 +4,6 @@ #include -#include - -#include #include #include #include @@ -16,7 +13,6 @@ #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" @@ -25,25 +21,17 @@ #include "../../weblist/WebListManager.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" -#include -#include -#include #include "helpers.h" +#include "./bulk_parsers.h" -#include #include -#include #include -#include #include #include -#include -#include #include #include -namespace fs = std::experimental::filesystem; using namespace std::chrono; using namespace std; using namespace ts; @@ -805,73 +793,43 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); } + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, false); - if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; - - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); - - bool conOnError = cmd[0].has("continueonerror"); - bool checkTp = false; - bool sgroupUpdate = false; - + bool update_talk_power{false}, update_server_group_list{false}; auto permissions = serverGroup->permissions(); - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); - //permvalue='1' permnegated='0' permskip='0' - - auto val = cmd[index]["permvalue"].as(); - if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if(!permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - permissions->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); - } else { - permissions->set_permission( - permType, - {cmd[index]["permvalue"], 0}, - permission::v2::PermissionUpdateType::set_value, - permission::v2::PermissionUpdateType::do_nothing, - - cmd[index]["permskip"].as() ? 1 : 0, - cmd[index]["permnegated"].as() ? 1 : 0 - ); - sgroupUpdate |= permission_is_group_property(permType); - checkTp |= permission_is_client_property(permType); - } + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value); + update_server_group_list |= ppermission.is_group_property(); + update_talk_power |= ppermission.is_client_view_property(); } - if(sgroupUpdate) + if(update_server_group_list) serverGroup->apply_properties_from_permissions(); //TODO may update for every server? if(this->server) { auto lock = this->_this.lock(); auto server = this->server; - threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() { - if(sgroupUpdate) + threads::Thread([update_talk_power, update_server_group_list, serverGroup, lock, server]() { + if(update_server_group_list) server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); - server->forEachClient([serverGroup, checkTp](shared_ptr cl) { + server->forEachClient([serverGroup, update_talk_power](shared_ptr cl) { if (cl->serverGroupAssigned(serverGroup)) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - if (checkTp) + if (update_talk_power) cl->updateChannelClientProperties(true, true); cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } }); }).detach(); } - return command_result{error::ok}; + + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) { @@ -890,49 +848,34 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); } - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); - bool conOnError = cmd[0].has("continueonerror"); - bool checkTp = false; - auto sgroupUpdate = false; - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if (grant) { - serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); - } else { - serverGroup->permissions()->set_permission( - permType, - permission::v2::empty_permission_values, - permission::v2::PermissionUpdateType::delete_value, - permission::v2::PermissionUpdateType::do_nothing - ); - sgroupUpdate |= permission_is_group_property(permType); - checkTp |= permission_is_client_property(permType); - } + 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); + update_server_group_list |= ppermission.is_group_property(); + update_talk_power |= ppermission.is_client_view_property(); } - if(sgroupUpdate) + if(update_server_group_list) serverGroup->apply_properties_from_permissions(); if(this->server) { auto lock = this->_this.lock(); auto server = this->server; - threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() { - if(sgroupUpdate) + threads::Thread([update_talk_power, update_server_group_list, serverGroup, lock, server]() { + if(update_server_group_list) server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); - server->forEachClient([serverGroup, checkTp](shared_ptr cl) { + server->forEachClient([serverGroup, update_talk_power](shared_ptr cl) { if (cl->serverGroupAssigned(serverGroup)) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - if (checkTp) + if (update_talk_power) cl->updateChannelClientProperties(true, true); cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } @@ -940,7 +883,7 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) { }).detach(); } - return command_result{error::ok}; + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& cmd) { @@ -970,67 +913,37 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& if(groups.empty()) return command_result{error::ok}; - auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, false); - if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); + bool update_clients{false}, update_server_group_list{false}; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + for(const auto& serverGroup : groups) + ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value); - bool conOnError = cmd[0].has("continueonerror"); - bool checkTp = false; - bool sgroupUpdate = false; - - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); - //permvalue='1' permnegated='0' permskip='0'end - - auto val = cmd[index]["permvalue"].as(); - if(permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - if(!ignore_granted_values && !permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - for(const auto& serverGroup : groups) { - if (grant) { - serverGroup->permissions()->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); - } else { - serverGroup->permissions()->set_permission( - permType, - {cmd[index]["permvalue"], 0}, - permission::v2::PermissionUpdateType::set_value, - permission::v2::PermissionUpdateType::do_nothing, - - cmd[index]["permskip"].as() ? 1 : 0, - cmd[index]["permnegated"].as() ? 1 : 0 - ); - } - } - sgroupUpdate |= permission_is_group_property(permType); - checkTp |= permission_is_client_property(permType); + update_server_group_list |= ppermission.is_group_property(); + update_clients |= ppermission.is_client_view_property(); } - if(sgroupUpdate) + if(update_server_group_list) for(auto& group : groups) group->apply_properties_from_permissions(); auto lock = this->_this.lock(); if(ref_server) { - threads::Thread([checkTp, sgroupUpdate, groups, lock, ref_server]() { - if(sgroupUpdate) + threads::Thread([update_clients, update_server_group_list, groups, lock, ref_server]() { + if(update_server_group_list) ref_server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); - ref_server->forEachClient([groups, checkTp](shared_ptr cl) { + ref_server->forEachClient([groups, update_clients](shared_ptr cl) { for(const auto& serverGroup : groups) { if (cl->serverGroupAssigned(serverGroup)) { if(cl->update_cached_permissions()) {/* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ } - if (checkTp) { + if (update_clients) { cl->updateChannelClientProperties(true, true); } cl->join_state_id++; /* join permission may changed, all channels need to be recalculate if needed */ @@ -1040,7 +953,8 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& }); }).detach(); } - return command_result{error::ok}; + + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& cmd) { @@ -1069,54 +983,37 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& if(groups.empty()) return command_result{error::ok}; - auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); + command::bulk_parser::PermissionBulksParser pparser{cmd}; + if(!pparser.validate(this->ref(), 0)) + return pparser.build_command_result(); - bool conOnError = cmd[0].has("continueonerror"); - bool checkTp = false; - auto sgroupUpdate = false; - for (int index = 0; index < cmd.bulkCount(); index++) { - PARSE_PERMISSION(cmd); + bool update_clients{false}, update_server_group_list{false}; + for(const auto& ppermission : pparser.iterate_valid_permissions()) { + for(const auto& serverGroup : groups) + ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value); - if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) { - if(conOnError) continue; - return command_result{permission::i_permission_modify_power}; - } - - for(const auto& serverGroup : groups) { - if (grant) { - serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); - } else { - serverGroup->permissions()->set_permission( - permType, - permission::v2::empty_permission_values, - permission::v2::PermissionUpdateType::delete_value, - permission::v2::PermissionUpdateType::do_nothing - ); - sgroupUpdate |= permission_is_group_property(permType); - } - } - checkTp |= permission_is_client_property(permType); + update_server_group_list |= ppermission.is_group_property(); + update_clients |= ppermission.is_client_view_property(); } - - if(sgroupUpdate) { + if(update_server_group_list) { for(auto& group : groups) group->apply_properties_from_permissions(); } if(ref_server) { auto lock = this->_this.lock(); - threads::Thread([checkTp, sgroupUpdate, groups, lock, ref_server]() { - if(sgroupUpdate) + threads::Thread([update_clients, update_server_group_list, groups, lock, ref_server]() { + if(update_server_group_list) ref_server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); - ref_server->forEachClient([groups, checkTp](shared_ptr cl) { + ref_server->forEachClient([groups, update_clients](shared_ptr cl) { for(const auto& serverGroup : groups) { if (cl->serverGroupAssigned(serverGroup)) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - if (checkTp) + if (update_clients) cl->updateChannelClientProperties(true, true); cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ break; @@ -1126,7 +1023,7 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& }).detach(); } - return command_result{error::ok}; + return pparser.build_command_result(); } command_result ConnectedClient::handleCommandServerGroupsByClientId(Command &cmd) { diff --git a/server/src/client/query/QueryClient.cpp b/server/src/client/query/QueryClient.cpp index 213f45b..e7a16ed 100644 --- a/server/src/client/query/QueryClient.cpp +++ b/server/src/client/query/QueryClient.cpp @@ -501,18 +501,18 @@ bool QueryClient::handleMessage(const pipes::buffer_view& message) { cmd = make_unique(Command::parse(pipes::buffer_view{(void*) command.data(), command.length()}, true, !ts::config::server::strict_ut8_mode)); } catch(std::invalid_argument& ex) { logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (invalid argument): {}", this->getLoggingPeerIp(), this->getPeerPort(), command); - error = command_result{error::parameter_convert}; + error.reset(command_result{error::parameter_convert}); goto handle_error; } catch(std::exception& ex) { logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (exception: {}): {}", this->getLoggingPeerIp(), this->getPeerPort(), ex.what(), command); - error = command_result{error::vs_critical, std::string{ex.what()}}; + error.reset(command_result{error::vs_critical, std::string{ex.what()}}); goto handle_error; } try { this->handleCommandFull(*cmd); } catch(std::exception& ex) { - error = command_result{error::vs_critical, std::string{ex.what()}}; + error.reset(command_result{error::vs_critical, std::string{ex.what()}}); goto handle_error; } return true; diff --git a/server/src/client/voice/VoiceClientPacketHandler.cpp b/server/src/client/voice/VoiceClientPacketHandler.cpp index a8aaad5..e56071e 100644 --- a/server/src/client/voice/VoiceClientPacketHandler.cpp +++ b/server/src/client/voice/VoiceClientPacketHandler.cpp @@ -21,18 +21,18 @@ void VoiceClient::handlePacketCommand(const pipes::buffer_view& command_string) try { command = make_unique(Command::parse(command_string, true, !ts::config::server::strict_ut8_mode)); } catch(std::invalid_argument& ex) { - result = command_result{error::parameter_convert, std::string{ex.what()}}; + result.reset(command_result{error::parameter_convert, std::string{ex.what()}}); goto handle_error; } catch(std::exception& ex) { - result = command_result{error::parameter_convert, std::string{ex.what()}}; + result.reset(command_result{error::parameter_convert, std::string{ex.what()}}); goto handle_error; } if(command->command() == "clientek") { - result = this->handleCommandClientEk(*command); + result.reset(this->handleCommandClientEk(*command)); if(result.has_error()) goto handle_error; } else if(command->command() == "clientinitiv") { - result = this->handleCommandClientInitIv(*command); + result.reset(this->handleCommandClientInitIv(*command)); if(result.has_error()) goto handle_error; } else this->handleCommandFull(*command, true); diff --git a/server/src/lincense/LicenseService.cpp b/server/src/lincense/LicenseService.cpp index a68bcf8..16cb4d6 100644 --- a/server/src/lincense/LicenseService.cpp +++ b/server/src/lincense/LicenseService.cpp @@ -539,7 +539,7 @@ void LicenseService::execute_tick() { auto now = std::chrono::system_clock::now(); if(this->request_state_ != request_state::empty) { if(this->timings.last_request + std::chrono::minutes{5} < now) { - this->handle_check_fail(strobf("Scheduling next check at {}").string()); + this->handle_check_fail(strobf("check timeout").string()); } else { return; } diff --git a/server/src/manager/LetterManager.cpp b/server/src/manager/LetterManager.cpp index 41c8a49..d1160f8 100644 --- a/server/src/manager/LetterManager.cpp +++ b/server/src/manager/LetterManager.cpp @@ -47,7 +47,7 @@ std::vector> LetterManager::avariableLetters(Clien if(strcmp(columns[index], "sender") == 0) letter->sender = values[index]; else if(strcmp(columns[index], "created") == 0) - letter->created = system_clock::now() + milliseconds(stoull(values[index])); + letter->created = std::chrono::system_clock::time_point{} + seconds{stoull(values[index])}; else if(strcmp(columns[index], "letterId") == 0) letter->id = static_cast(stoull(values[index])); else if(strcmp(columns[index], "subject") == 0) diff --git a/shared b/shared index ee4c754..b60608f 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit ee4c7540f011d17cdac9c170e02c60296ae2487a +Subproject commit b60608ff94b06145bc808426392871ebd95fe9d3