diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ad737e..4e5f926 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -241,5 +241,8 @@ if(BUILD_TESTS) add_executable(LinkedTest test/LinkedTest.cpp ${SOURCE_FILES}) target_link_libraries(LinkedTest ${TEST_LIBRARIES}) + + add_executable(PermissionTest test/PermissionTest.cpp ${SOURCE_FILES}) + target_link_libraries(PermissionTest ${TEST_LIBRARIES}) endif() endif() diff --git a/src/BasicChannel.cpp b/src/BasicChannel.cpp index 7125351..21dbb7c 100644 --- a/src/BasicChannel.cpp +++ b/src/BasicChannel.cpp @@ -21,34 +21,9 @@ BasicChannel::BasicChannel(ChannelId parentId, ChannelId channelId) { this->properties()[property::CHANNEL_PID] = parentId; } -void BasicChannel::setPermissionManager(const std::shared_ptr& manager) { - auto handler = [&](std::shared_ptr perm){ - if(perm->type->type == permission::i_icon_id) - this->properties()[property::CHANNEL_ICON_ID] = (IconId) (perm->hasValue() ? perm->value : 0); - else if(perm->type->type == permission::i_client_needed_talk_power) { - this->properties()[property::CHANNEL_NEEDED_TALK_POWER] = (perm->hasValue() ? perm->value : 0); - } - }; - manager->registerUpdateHandler(handler); - //Update channel properties - for(const auto& perm : manager->getPermission(permission::i_icon_id, nullptr)) handler(perm); - for(const auto& perm : manager->getPermission(permission::i_client_needed_talk_power, nullptr)) handler(perm); - +void BasicChannel::setPermissionManager(const std::shared_ptr& manager) { this->_permissions = manager; - /* - this->_permissions->registerPermission(permission::i_channel_needed_permission_modify_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_channel_needed_join_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_channel_needed_subscribe_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_channel_needed_description_view_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_channel_needed_modify_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_channel_needed_delete_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_ft_needed_file_browse_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_ft_needed_file_upload_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_ft_needed_file_download_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_ft_needed_file_rename_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_ft_needed_directory_create_power, 0, nullptr, PERM_FLAG_PUBLIC); - this->_permissions->registerPermission(permission::i_icon_id, 0, nullptr, PERM_FLAG_PUBLIC); - */ + this->update_properties_from_permissions(); } void BasicChannel::setProperties(const std::shared_ptr& props) { @@ -73,6 +48,37 @@ void BasicChannel::setProperties(const std::shared_ptr& props) { this->_channel_id = this->channelId(); } +std::vector BasicChannel::update_properties_from_permissions() { + std::vector result; + result.reserve(2); + + auto permission_manager = this->permissions(); /* keeps the manager until we've finished our calculations */ + /* update the icon id */ + { + IconId target_icon_id = 0; + auto& permission_icon_flags = permission_manager->permission_flags(permission::i_icon_id); + if(permission_icon_flags.value_set) + target_icon_id = (IconId) permission_manager->permission_values(permission::i_icon_id).value; + if(this->properties()[property::CHANNEL_ICON_ID] != target_icon_id) { + this->properties()[property::CHANNEL_ICON_ID] = target_icon_id; + result.push_back(property::CHANNEL_ICON_ID); + } + } + /* update the channel talk power */ + { + permission::PermissionValue talk_power = 0; + auto& permission_tp_flags = permission_manager->permission_flags(permission::i_client_needed_talk_power); + if(permission_tp_flags.value_set) + talk_power = (IconId) permission_manager->permission_values(permission::i_client_needed_talk_power).value; + if(this->properties()[property::CHANNEL_NEEDED_TALK_POWER] != talk_power) { + this->properties()[property::CHANNEL_NEEDED_TALK_POWER] = talk_power; + result.push_back(property::CHANNEL_NEEDED_TALK_POWER); + } + } + + return result; +} + std::shared_ptr BasicChannel::parent() { auto link_lock = this->_link.lock(); if(!link_lock) diff --git a/src/BasicChannel.h b/src/BasicChannel.h index 7da4ef9..60b32a2 100644 --- a/src/BasicChannel.h +++ b/src/BasicChannel.h @@ -58,8 +58,32 @@ namespace ts { return std::chrono::system_clock::time_point() + std::chrono::milliseconds(this->properties()[property::CHANNEL_CREATED_AT].as()); } - inline std::shared_ptr permissions(){ return this->_permissions; } - virtual void setPermissionManager(const std::shared_ptr&); + ts_always_inline bool permission_require_property_update(const permission::PermissionType& permission) { + return permission == permission::i_icon_id || permission == permission::i_client_needed_talk_power; + } + std::vector update_properties_from_permissions(); + + inline bool permission_granted(const permission::PermissionType& permission, const permission::v2::PermissionFlaggedValue& granted_value, bool require_granted_value) { + auto permission_manager = this->permissions(); /* copy the manager */ + assert(permission_manager); + const auto data = permission_manager->permission_value_flagged(permission); + if(!data.has_value) { + return !require_granted_value || granted_value.has_value; + } + if(!granted_value.has_value) { + return false; + } + if(data.value == -1) { + return granted_value.value == -1; + } + return granted_value.value >= data.value; + } + ts_always_inline bool talk_power_granted(const permission::v2::PermissionFlaggedValue& granted_value) { + return this->permission_granted(permission::i_client_needed_talk_power, granted_value, false); + } + + ts_always_inline std::shared_ptr permissions(){ return this->_permissions; } + virtual void setPermissionManager(const std::shared_ptr&); virtual void setProperties(const std::shared_ptr&); protected: public: @@ -71,7 +95,7 @@ namespace ts { protected: std::weak_ptr _link; std::shared_ptr _properties; - std::shared_ptr _permissions; + std::shared_ptr _permissions; ChannelId _channel_order = 0; ChannelId _channel_id = 0; diff --git a/src/Definitions.h b/src/Definitions.h index 8a0729a..353f8f2 100644 --- a/src/Definitions.h +++ b/src/Definitions.h @@ -152,4 +152,6 @@ DEFINE_TRANSFORMS(ts::server::ClientType, uint8_t); DEFINE_TRANSFORMS(ts::LicenseType, uint8_t); DEFINE_TRANSFORMS(ts::PluginTargetMode, uint8_t); DEFINE_TRANSFORMS(ts::ViewReasonId, uint8_t); -DEFINE_TRANSFORMS(ts::ChatMessageMode, uint8_t); \ No newline at end of file +DEFINE_TRANSFORMS(ts::ChatMessageMode, uint8_t); + +#define ts_always_inline inline __attribute__((always_inline)) \ No newline at end of file diff --git a/src/PermissionManager.cpp b/src/PermissionManager.cpp index b0bd14b..5e713ae 100644 --- a/src/PermissionManager.cpp +++ b/src/PermissionManager.cpp @@ -10,7 +10,6 @@ using namespace ts::permission; deque> ts::permission::availablePermissions = deque>{ make_shared(PermissionType::b_serverinstance_help_view, PermissionGroup::global_info, "b_serverinstance_help_view", "Retrieve information about ServerQuery commands"), - //make_shared(PermissionType::b_serverinstance_help_view_teespeack, PermissionGroup::group_7, "b_serverinstance_help_view_teespeack", "Costume teespeck help command idk ist just a test!"), make_shared(PermissionType::b_serverinstance_version_view, PermissionGroup::global_info, "b_serverinstance_version_view", "Retrieve global server version (including platform and build number)"), make_shared(PermissionType::b_serverinstance_info_view, PermissionGroup::global_info, "b_serverinstance_info_view", "Retrieve global server information"), make_shared(PermissionType::b_serverinstance_virtualserver_list, PermissionGroup::global_info, "b_serverinstance_virtualserver_list", "List virtual servers stored in the sql"), @@ -18,7 +17,6 @@ deque> ts::permission::availablePermissions //Removed due its useless make_shared(PermissionType::b_serverinstance_permission_list, PermissionGroup::global_info, "b_serverinstance_permission_list", "List permissions available available on the server instance"), make_shared(PermissionType::b_serverinstance_permission_find, PermissionGroup::global_info, "b_serverinstance_permission_find", "Search permission assignments by name or ID"), - //make_shared(PermissionType::b_serverinstance_allow_teaspeak_plugin, PermissionGroup::group_7, "b_serverinstance_allow_teespeak_plugin", "Allow a user to use the TeaSpeak plugin extension"), make_shared(PermissionType::b_virtualserver_create, PermissionGroup::global_vsmanage, "b_virtualserver_create", "Create virtual servers"), make_shared(PermissionType::b_virtualserver_delete, PermissionGroup::global_vsmanage, "b_virtualserver_delete", "Delete virtual servers"), make_shared(PermissionType::b_virtualserver_start_any, PermissionGroup::global_vsmanage, "b_virtualserver_start_any", "Start any virtual server in the server instance"), @@ -620,20 +618,39 @@ std::deque permission::availableGroups = { std::shared_ptr PermissionTypeEntry::unknown = make_shared(PermissionType::unknown, PermissionGroup::global, "unknown", "unknown"); -std::shared_ptr permission::resolvePermissionData(PermissionType type){ - if((type & PERM_ID_GRANT) > 0) type &= ~PERM_ID_GRANT; - for(auto& elm : availablePermissions) - if(elm->type == type) return elm; - return PermissionTypeEntry::unknown; +vector> permission_id_map; +void permission::setup_permission_resolve() { + permission_id_map.resize(permission::permission_id_max); + + for(auto& permission : availablePermissions) { + if(!permission->clientSupported || permission->type < 0 || permission->type > permission::permission_id_max) + continue; + permission_id_map[permission->type] = permission; + } + + /* fix "holes" as well set the permission id 0 (unknown) */ + for(auto& permission : permission_id_map) { + if(permission) + continue; + permission = PermissionTypeEntry::unknown; + } +} + +std::shared_ptr permission::resolvePermissionData(PermissionType type) { + if((type & PERM_ID_GRANT) > 0) + type &= ~PERM_ID_GRANT; + + assert(!permission_id_map.empty()); + if(type < 0 || type >= permission::permission_id_max) + return PermissionTypeEntry::unknown; + + return permission_id_map[type]; } std::shared_ptr permission::resolvePermissionData(const std::string& name) { - for(auto& elm : availablePermissions) { - if(elm->name == name || elm->grant_name == name) { + for(auto& elm : availablePermissions) + if(elm->name == name || elm->grant_name == name) return elm; - } - } - debugMessage(LOG_GENERAL, "Could not find permission {}.", name); return PermissionTypeEntry::unknown; } @@ -1007,4 +1024,445 @@ deque update::migrate = { AQB("b_channel_ignore_join_power") AQB("b_virtualserver_select_godmode") AQB("b_client_ban_trigger_list") -}; \ No newline at end of file +}; + +v2::PermissionManager::PermissionManager() { + memset(this->block_use_count, 0, sizeof(this->block_use_count)); + memset(this->block_containers, 0, sizeof(this->block_containers)); +} + +v2::PermissionManager::~PermissionManager() { + for(auto& block : this->block_containers) + delete block; +} + +void v2::PermissionManager::load_permission(const ts::permission::PermissionType &permission, const ts::permission::v2::PermissionValues &values, bool flag_skip, bool flag_negate, bool flag_value, bool flag_grant) { + if(permission < 0 || permission >= PermissionType::permission_id_max) + return; + + const auto block = this->calculate_block(permission); + this->ref_allocate_block(block); + + auto& data = this->block_containers[block]->permissions[this->calculate_block_index(permission)]; + data.values = values; + data.flags.database_reference = true; + data.flags.skip = flag_skip; + data.flags.negate = flag_negate; + data.flags.value_set = flag_value; + data.flags.grant_set = flag_grant; + this->unref_block(block); +} + +void v2::PermissionManager::load_permission(const ts::permission::PermissionType &permission, const ts::permission::v2::PermissionValues &values, ChannelId channel_id, bool flag_skip, bool flag_negate, bool flag_value, bool flag_grant) { + if(permission < 0 || permission >= PermissionType::permission_id_max) + return; + + unique_lock channel_perm_lock(this->channel_list_lock); + ChannelPermissionContainer* permission_container = nullptr; + for(auto& entry : this->_channel_permissions) + if(entry->permission == permission && entry->channel_id == channel_id) { + permission_container = &*entry; + break; + } + + if(!permission_container) { + auto container = make_unique(); + container->permission = permission; + container->channel_id = channel_id; + permission_container = &*container; + this->_channel_permissions.push_back(std::move(container)); + + /* now set the channel flag for that permission */ + const auto block = this->calculate_block(permission); + this->ref_allocate_block(block); + + auto& data = this->block_containers[block]->permissions[this->calculate_block_index(permission)]; + data.flags.channel_specific = true; + this->unref_block(block); + } + + permission_container->values = values; + permission_container->flags.database_reference = true; + permission_container->flags.skip = flag_skip; + permission_container->flags.negate = flag_negate; + permission_container->flags.value_set = flag_value; + permission_container->flags.grant_set = flag_grant; +} + +static constexpr v2::PermissionFlags empty_flags = {false, false, false, false, false, false, 0}; +const v2::PermissionFlags v2::PermissionManager::permission_flags(const ts::permission::PermissionType &permission) { + if(permission < 0 || permission >= PermissionType::permission_id_max) + return empty_flags; + + const auto block = this->calculate_block(permission); + if(!this->ref_block(block)) + return empty_flags; + + PermissionFlags result{this->block_containers[block]->permissions[this->calculate_block_index(permission)].flags}; + this->unref_block(block); + return result; +} + +const v2::PermissionValues v2::PermissionManager::permission_values(const ts::permission::PermissionType &permission) { + if(permission < 0 || permission >= PermissionType::permission_id_max) + return v2::empty_permission_values; + + const auto block = this->calculate_block(permission); + if(!this->ref_block(block)) + return v2::empty_permission_values; /* TODO: may consider to throw an exception because the existence should be checked by getting the permission flags */ + + v2::PermissionValues data{this->block_containers[block]->permissions[this->calculate_block_index(permission)].values}; + this->unref_block(block); + return data; +} + +const v2::PermissionFlaggedValue v2::PermissionManager::permission_value_flagged(const ts::permission::PermissionType &permission) { + if(permission < 0 || permission >= PermissionType::permission_id_max) + return v2::empty_permission_flagged_value; + + const auto block = this->calculate_block(permission); + if(!this->ref_block(block)) + return v2::empty_permission_flagged_value; + + auto& data = this->block_containers[block]->permissions[this->calculate_block_index(permission)]; + v2::PermissionFlaggedValue result{data.values.value, data.flags.value_set}; + this->unref_block(block); + return result; +} + +const v2::PermissionFlaggedValue v2::PermissionManager::permission_granted_flagged(const ts::permission::PermissionType &permission) { + if(permission < 0 || permission >= PermissionType::permission_id_max) + return v2::empty_permission_flagged_value; + + const auto block = this->calculate_block(permission); + if(!this->ref_block(block)) + return v2::empty_permission_flagged_value; + + auto& data = this->block_containers[block]->permissions[this->calculate_block_index(permission)]; + v2::PermissionFlaggedValue result{data.values.grant, data.flags.grant_set}; + this->unref_block(block); + return result; +} + +static constexpr v2::PermissionContainer empty_channel_permission = {empty_flags, v2::empty_permission_values}; +const v2::PermissionContainer v2::PermissionManager::channel_permission(const PermissionType &permission, ts::ChannelId channel_id) { + if(permission < 0 || permission >= PermissionType::permission_id_max) + return empty_channel_permission; + + shared_lock channel_perm_lock(this->channel_list_lock); + for(auto& entry : this->_channel_permissions) + if(entry->permission == permission && entry->channel_id == channel_id) + return v2::PermissionContainer{entry->flags, entry->values}; + return empty_channel_permission; +} + +void v2::PermissionManager::set_permission(const PermissionType &permission, const v2::PermissionValues &values, const v2::PermissionUpdateType &action_value, const v2::PermissionUpdateType &action_grant, int flag_skip, int flag_negate) { + if(permission < 0 || permission >= PermissionType::permission_id_max) + return; + + const auto block = this->calculate_block(permission); + this->ref_allocate_block(block); + + auto& data = this->block_containers[block]->permissions[this->calculate_block_index(permission)]; + if(action_value == v2::PermissionUpdateType::set_value) { + data.flags.value_set = true; + data.flags.flag_value_update = true; + data.values.value = values.value; + } else if(action_value == v2::PermissionUpdateType::delete_value) { + data.flags.value_set = false; + data.flags.flag_value_update = true; + data.values.grant = permNotGranted; /* required for the database else it does not "deletes" the value */ + } + + if(action_grant == v2::PermissionUpdateType::set_value) { + data.flags.grant_set = true; + data.flags.flag_grant_update = true; + data.values.grant = values.value; + } else if(action_grant == v2::PermissionUpdateType::delete_value) { + data.flags.grant_set = false; + data.flags.flag_grant_update = true; + data.values.grant = permNotGranted; /* required for the database else it does not "deletes" the value */ + } + + if(flag_skip >= 0) + data.flags.skip = flag_skip == 1; + + if(flag_negate >= 0) + data.flags.skip = flag_negate == 1; + this->unref_block(block); + this->trigger_db_update(); +} + +void v2::PermissionManager::set_channel_permission(const PermissionType &permission, ChannelId channel_id, const v2::PermissionValues &values, const v2::PermissionUpdateType &action_value, const v2::PermissionUpdateType &action_grant, int flag_skip, int flag_negate) { + if(permission < 0 || permission >= PermissionType::permission_id_max) + return; + + unique_lock channel_perm_lock(this->channel_list_lock); + ChannelPermissionContainer* permission_container = nullptr; + for(auto& entry : this->_channel_permissions) + if(entry->permission == permission && entry->channel_id == channel_id) { + permission_container = &*entry; + break; + } + + /* register a new permission if we have no permission already*/ + if(!permission_container || !permission_container->flags.permission_set()) { /* if the permission isn't set then we have to register it again */ + if(action_value != v2::PermissionUpdateType::set_value && action_grant == v2::PermissionUpdateType::set_value) { + return; /* we were never willing to set this permission */ + } + + if(!permission_container) { + auto container = make_unique(); + container->permission = permission; + container->channel_id = channel_id; + permission_container = &*container; + this->_channel_permissions.push_back(std::move(container)); + } + + /* now set the channel flag for that permission */ + const auto block = this->calculate_block(permission); + this->ref_allocate_block(block); + + auto& data = this->block_containers[block]->permissions[this->calculate_block_index(permission)]; + data.flags.channel_specific = true; + this->unref_block(block); + } + + if(action_value == v2::PermissionUpdateType::set_value) { + permission_container->flags.value_set = true; + permission_container->flags.flag_value_update = true; + permission_container->values.value = values.value; + } else if(action_value == v2::PermissionUpdateType::delete_value) { + permission_container->flags.value_set = false; + permission_container->flags.flag_value_update = true; + permission_container->values.grant = permNotGranted; /* required for the database else it does not "deletes" the value */ + } + + if(action_grant == v2::PermissionUpdateType::set_value) { + permission_container->flags.grant_set = true; + permission_container->flags.flag_grant_update = true; + permission_container->values.grant = values.value; + } else if(action_grant == v2::PermissionUpdateType::delete_value) { + permission_container->flags.grant_set = false; + permission_container->flags.flag_grant_update = true; + permission_container->values.grant = permNotGranted; /* required for the database else it does not "deletes" the value */ + } + + if(flag_skip >= 0) + permission_container->flags.skip = flag_skip == 1; + + if(flag_negate >= 0) + permission_container->flags.skip = flag_negate == 1; + + if(!permission_container->flags.permission_set()) { /* unregister the permission again because its unset, we delete the channel permission as soon we've flushed the updates */ + auto other_channel_permission = std::find_if(this->_channel_permissions.begin(), this->_channel_permissions.end(), [&](unique_ptr& perm) { return perm->permission == permission && perm->flags.permission_set(); }); + if(other_channel_permission == this->_channel_permissions.end()) { /* no more channel specific permissions c*/ + const auto block = this->calculate_block(permission); + if(this->ref_block(block)) { + this->block_containers[block]->permissions[this->calculate_block_index(permission)].flags.channel_specific = false; + this->unref_block(block); + } + } + } + this->trigger_db_update(); +} + +const std::vector> v2::PermissionManager::permissions() { + std::unique_lock use_lock(this->block_use_count_lock); + decltype(this->block_containers) block_containers; /* save the states/nullptr's */ + memcpy(block_containers, this->block_containers, sizeof(this->block_containers)); + size_t block_count = 0; + for(size_t index = 0; index < BULK_COUNT; index++) { + if(block_containers[index]) { + block_count++; + this->block_use_count[index]++; + } + } + use_lock.unlock(); + + vector> result; + result.reserve(block_count * PERMISSIONS_BULK_ENTRY_COUNT); + + for(size_t block_index = 0; block_index < BULK_COUNT; block_index++) { + auto& block = block_containers[block_index]; + if(!block) + continue; + + for(size_t permission_index = 0; permission_index < PERMISSIONS_BULK_ENTRY_COUNT; permission_index++) { + auto& permission = block->permissions[permission_index]; + if(!permission.flags.permission_set()) + continue; + + result.emplace_back((PermissionType) (block_index * PERMISSIONS_BULK_ENTRY_COUNT + permission_index), permission); + } + } + result.shrink_to_fit(); + + use_lock.lock(); + for(size_t index = 0; index < BULK_COUNT; index++) { + if(block_containers[index]) + this->block_use_count[index]--; + } + use_lock.unlock(); + + return result; +} + +const vector> v2::PermissionManager::channel_permissions(ts::ChannelId channel_id) { + shared_lock channel_perm_lock(this->channel_list_lock); + + vector> result; + for(auto& entry : this->_channel_permissions) + if((channel_id == entry->channel_id) && (entry->flags.value_set || entry->flags.grant_set)) + result.emplace_back(entry->permission, v2::PermissionContainer{entry->flags, entry->values}); + return result; +} + +const vector> v2::PermissionManager::channel_permissions() { + shared_lock channel_perm_lock(this->channel_list_lock); + + vector> result; + for(auto& entry : this->_channel_permissions) + if(entry->flags.value_set || entry->flags.grant_set) + result.emplace_back(entry->permission, entry->channel_id, v2::PermissionContainer{entry->flags, entry->values}); + return result; +} + +const std::vector v2::PermissionManager::flush_db_updates() { + if(!this->requires_db_save) + return {}; + + this->requires_db_save = false; + std::vector result; + + { + lock_guard use_lock(this->block_use_count_lock); + size_t block_count = 0; + for (auto &block_container : block_containers) + if (block_container) + block_count++; + result.reserve(block_count * PERMISSIONS_BULK_ENTRY_COUNT); + + for(size_t block_index = 0; block_index < BULK_COUNT; block_index++) { + auto& block = block_containers[block_index]; + if(!block) + continue; + + for(size_t permission_index = 0; permission_index < PERMISSIONS_BULK_ENTRY_COUNT; permission_index++) { + auto& permission = block->permissions[permission_index]; + + if(!permission.flags.flag_value_update && !permission.flags.flag_grant_update) + continue; + + /* we only need an update it the permission has a DB reference or we will set the permission */ + if(permission.flags.database_reference || permission.flags.permission_set()) { + /* + PermissionType permission; + ChannelId channel_id; + + PermissionValues values; + PermissionUpdateType update_value; + PermissionUpdateType update_grant; + + bool flag_new: 1; + bool flag_skip: 1; + bool flag_negate: 1; + */ + result.push_back(v2::PermissionDBUpdateEntry{ + (PermissionType) (block_index * PERMISSIONS_BULK_ENTRY_COUNT + permission_index), + (ChannelId) 0, + + permission.values, + (PermissionUpdateType) (permission.flags.flag_value_update ? (permission.flags.value_set ? PermissionUpdateType::set_value : PermissionUpdateType::delete_value) : PermissionUpdateType::do_nothing), + (PermissionUpdateType) (permission.flags.flag_grant_update ? (permission.flags.grant_set ? PermissionUpdateType::set_value : PermissionUpdateType::delete_value) : PermissionUpdateType::do_nothing), + + (bool) permission.flags.database_reference, + (bool) !permission.flags.permission_set(), /* db delete */ + (bool) permission.flags.skip, + (bool) permission.flags.negate + }); + permission.flags.database_reference = permission.flags.permission_set(); + } + + permission.flags.flag_value_update = false; + permission.flags.flag_grant_update = false; + } + } + } + { + lock_guard chanel_lock(this->channel_list_lock); + for(size_t index = 0; index < this->_channel_permissions.size(); index++) { + auto& permission = this->_channel_permissions[index]; + if(!permission->flags.flag_value_update && !permission->flags.flag_grant_update) + continue; + + + /* we only need an update it the permission has a DB reference or we will set the permission */ + if(permission->flags.database_reference || permission->flags.permission_set()) { + result.push_back( + v2::PermissionDBUpdateEntry{ + permission->permission, + permission->channel_id, + + permission->values, + permission->flags.flag_value_update ? (permission->flags.value_set ? PermissionUpdateType::set_value : PermissionUpdateType::delete_value) : PermissionUpdateType::do_nothing, + permission->flags.flag_grant_update ? (permission->flags.grant_set ? PermissionUpdateType::set_value : PermissionUpdateType::delete_value) : PermissionUpdateType::do_nothing, + + (bool) permission->flags.database_reference, + (bool) !permission->flags.permission_set(), /* db delete */ + (bool) permission->flags.skip, + (bool) permission->flags.negate + } + ); + permission->flags.database_reference = permission->flags.permission_set(); + } + + permission->flags.flag_value_update = false; + permission->flags.flag_grant_update = false; + if(!permission->flags.permission_set()) { + this->_channel_permissions.erase(this->_channel_permissions.begin() + index); + index--; + } + } + } + + return result; +} + +size_t v2::PermissionManager::used_memory() { + size_t result = sizeof(*this); + + for (auto &block_container : block_containers) { + if (block_container) + result += sizeof(PermissionContainerBulk); + } + + { + + shared_lock channel_lock(this->channel_list_lock); + result += this->_channel_permissions.size() * (sizeof(ChannelPermissionContainer) + sizeof(unique_ptr)); + } + + return result; +} + +void v2::PermissionManager::cleanup() { + lock_guard use_lock(this->block_use_count_lock); + + for (auto &block_container : block_containers) { + if (!block_container) continue; + + bool used = false; + for(auto& permission : block_container->permissions) { + if(permission.flags.value_set || permission.flags.grant_set || permission.flags.channel_specific) { + used = true; + break; + } + } + if(used) + continue; + + delete block_container; + block_container = nullptr; + } +} diff --git a/src/PermissionManager.h b/src/PermissionManager.h index 1664572..d51bb23 100644 --- a/src/PermissionManager.h +++ b/src/PermissionManager.h @@ -1,11 +1,18 @@ #pragma once +#include +#include #include #include #include #include #include #include +#include +#include +#include +#include /* for memset */ +#include "./misc/spin_lock.h" #include "Definitions.h" #include "Variable.h" @@ -447,7 +454,9 @@ namespace ts { i_ft_directory_create_power, i_ft_needed_directory_create_power, i_ft_quota_mb_download_per_client, - i_ft_quota_mb_upload_per_client + i_ft_quota_mb_upload_per_client, + + permission_id_max }; inline PermissionType& operator&=(PermissionType& a, int b) { return a = (PermissionType) ((int) a & b); } @@ -603,6 +612,7 @@ namespace ts { extern std::deque neededPermissions; extern std::deque availableGroups; + void setup_permission_resolve(); std::shared_ptr resolvePermissionData(PermissionType); std::shared_ptr resolvePermissionData(const std::string&); @@ -620,7 +630,6 @@ namespace ts { } - Permission(Permission &) = delete; Permission(const Permission &) = delete; @@ -717,6 +726,178 @@ namespace ts { std::deque> permissions; std::deque)>> updateHandler; }; + + namespace v2 { + #pragma pack(push, 1) + struct PermissionFlags { + bool database_reference: 1; /* if set the permission is known within the database, else it has tp be inserted */ + bool channel_specific: 1; /* set if there are channel specific permissions */ + + bool value_set: 1; + bool grant_set: 1; + + bool skip: 1; + bool negate: 1; + + bool flag_value_update: 1; + bool flag_grant_update: 1; + + ts_always_inline bool permission_set() { return this->value_set || this->grant_set; } + }; + static_assert(sizeof(PermissionFlags) == 1); + + struct PermissionValues { + PermissionValue value; + PermissionValue grant; + }; + static_assert(sizeof(PermissionValues) == 8); + static constexpr PermissionValues empty_permission_values{0, 0}; + + struct PermissionContainer { + PermissionFlags flags; + PermissionValues values; + }; + static_assert(sizeof(PermissionContainer) == 9); + + struct ChannelPermissionContainer : public PermissionContainer { + PermissionType permission; + ChannelId channel_id; + }; + static_assert(sizeof(ChannelPermissionContainer) == 19); + + #pragma pack(pop) + + #pragma pack(push, 1) + template + struct PermissionContainerBulk { + PermissionContainer permissions[element_count]; + + PermissionContainerBulk() { + memset(this->permissions, 0, sizeof(this->permissions)); + } + }; + #pragma pack(pop) + + enum PermissionUpdateType { + do_nothing, + set_value, + delete_value + }; + + struct PermissionDBUpdateEntry { + PermissionType permission; + ChannelId channel_id; + + PermissionValues values; + PermissionUpdateType update_value; + PermissionUpdateType update_grant; + + bool flag_db: 1; /* only needs an update if set */ + bool flag_delete: 1; + bool flag_skip: 1; + bool flag_negate: 1; + }; + + struct PermissionFlaggedValue { + PermissionValue value; + bool has_value; + }; + static constexpr PermissionFlaggedValue empty_permission_flagged_value{0, false}; + + class PermissionManager { + public: + static constexpr size_t PERMISSIONS_BULK_BITS = 4; /* 16 permissions per block */ + static constexpr size_t PERMISSIONS_BULK_ENTRY_COUNT = 1 << PERMISSIONS_BULK_BITS; + static constexpr size_t BULK_COUNT = (PermissionType::permission_id_max / (1 << PERMISSIONS_BULK_BITS)) + ((PermissionType::permission_id_max % PERMISSIONS_BULK_ENTRY_COUNT == 0) ? 0 : 1); + static_assert(PERMISSIONS_BULK_ENTRY_COUNT * BULK_COUNT >= PermissionType::permission_id_max); + + PermissionManager(); + virtual ~PermissionManager(); + + /* load permissions from the database */ + void load_permission(const PermissionType&, const PermissionValues& /* values */, bool /* flag negate */, bool /* flag skip */, bool /* value present */,bool /* grant present */); + void load_permission(const PermissionType&, const PermissionValues& /* values */, ChannelId /* channel */, bool /* flag negate */, bool /* flag skip */, bool /* value present */,bool /* grant present */); + + /* general getters/setters */ + const PermissionFlags permission_flags(const PermissionType&); /* we return a "copy" because the actual permission could be deleted while we're analyzing the flags */ + ts_always_inline const PermissionFlags permission_flags(const std::shared_ptr& permission_info) { return this->permission_flags(permission_info->type); } + + const PermissionValues permission_values(const PermissionType&); + ts_always_inline const PermissionValues permission_values(const std::shared_ptr& permission_info) { return this->permission_values(permission_info->type); } + + const PermissionFlaggedValue permission_value_flagged(const PermissionType&); + ts_always_inline const PermissionFlaggedValue permission_value_flagged(const std::shared_ptr& permission_info) { return this->permission_value_flagged(permission_info->type); } + + const PermissionFlaggedValue permission_granted_flagged(const PermissionType&); + ts_always_inline const PermissionFlaggedValue permission_granted_flagged(const std::shared_ptr& permission_info) { return this->permission_granted_flagged(permission_info->type); } + + /* only worth looking up if channel_specific is set */ + const PermissionContainer channel_permission(const PermissionType& /* permission */, ChannelId /* channel id */); + ts_always_inline const PermissionContainer channel_permission(const std::shared_ptr& permission_info, ChannelId channel_id) { return this->channel_permission(permission_info->type, channel_id); } + + /* modifiers */ + void set_permission(const PermissionType& /* permission */, const PermissionValues& /* values */, const PermissionUpdateType& /* update value */, const PermissionUpdateType& /* update grant */, int /* flag negate */ = -1, int /* flag skip */ = -1); + void set_channel_permission(const PermissionType& /* permission */, ChannelId /* channel id */, const PermissionValues& /* values */, const PermissionUpdateType& /* update value */, const PermissionUpdateType& /* update grant */, int /* flag negate */ = -1, int /* flag skip */ = -1); + + /* bulk info */ + const std::vector> permissions(); + const std::vector> channel_permissions(ChannelId /* channel id */); + const std::vector> channel_permissions(); + + size_t used_memory(); + void cleanup(); + + ts_always_inline bool require_db_updates() { return this->requires_db_save; } + const std::vector flush_db_updates(); + private: + static constexpr size_t PERMISSIONS_BULK_BLOCK_MASK = (~(1 << PERMISSIONS_BULK_BITS)) & ((1 << PERMISSIONS_BULK_BITS) - 1); + + bool requires_db_save = false; + ts_always_inline void trigger_db_update() { this->requires_db_save = true; } /* todo: pull some kind of trigger? */ + + spin_lock block_use_count_lock{}; + int16_t block_use_count[BULK_COUNT]; + PermissionContainerBulk* block_containers[BULK_COUNT]; + + std::shared_mutex channel_list_lock{}; + std::deque> _channel_permissions{}; + + ts_always_inline size_t calculate_block(const PermissionType& permission) { + return permission >> PERMISSIONS_BULK_BITS; + } + + ts_always_inline size_t calculate_block_index(const PermissionType& permission) { + return permission & PERMISSIONS_BULK_BLOCK_MASK; + } + + /** + * @param block + * @return true if block exists else false + */ + ts_always_inline bool ref_block(size_t block) { + std::lock_guard use_lock(this->block_use_count_lock); + if(!this->block_containers[block]) + return false; + this->block_use_count[block]++; + assert(this->block_use_count[block] > 0); + return true; + } + + ts_always_inline void unref_block(size_t block) { + std::lock_guard use_lock(this->block_use_count_lock); + this->block_use_count[block]--; + assert(this->block_use_count[block] >= 0); + } + + ts_always_inline void ref_allocate_block(size_t block) { + std::lock_guard use_lock(this->block_use_count_lock); + if(!this->block_containers[block]) + this->block_containers[block] = new PermissionContainerBulk(); + this->block_use_count[block]++; + assert(this->block_use_count[block] > 0); + } + }; + } } } diff --git a/src/misc/spin_lock.h b/src/misc/spin_lock.h index a710a1f..a573a44 100644 --- a/src/misc/spin_lock.h +++ b/src/misc/spin_lock.h @@ -34,4 +34,6 @@ class spin_lock { always_inline void unlock() { locked.store(false, std::memory_order_release); } -}; \ No newline at end of file +}; + +#undef always_inline \ No newline at end of file diff --git a/test/PermissionTest.cpp b/test/PermissionTest.cpp new file mode 100644 index 0000000..29286af --- /dev/null +++ b/test/PermissionTest.cpp @@ -0,0 +1,58 @@ +// +// Created by wolverindev on 15.07.19. +// + +#include "PermissionManager.h" +#include + +using namespace std; +using namespace ts::permission::v2; +using PermissionType = ts::permission::PermissionType; + +void print_permissions(PermissionManager& manager) { + { + auto permissions = manager.permissions(); + cout << "Permissions: " << permissions.size() << endl; + for(const auto& permission : permissions) { + cout << " - " << ts::permission::resolvePermissionData(std::get<0>(permission))->name + ": "; + cout << (std::get<1>(permission).flags.value_set ? to_string(std::get<1>(permission).values.value) : "no value") << " negate: " << std::get<1>(permission).flags.negate << " skip: " << std::get<1>(permission).flags.skip << " "; + cout << "chan permission: " << std::get<1>(permission).flags.channel_specific << endl; + } + } + cout << "Used memory: " << manager.used_memory() << endl; +} + +void print_updates(PermissionManager& manager) { + const auto updates = manager.flush_db_updates(); + cout << "Permission updates: " << updates.size() << endl; + for(auto& update : updates) { + cout << "Permission: " << ts::permission::resolvePermissionData(update.permission)->name << "; Channel: " << update.channel_id << "; DB Ref: " << update.flag_db << endl; + cout << " value: " << (update.update_value == PermissionUpdateType::do_nothing ? "do nothing" : update.update_value == PermissionUpdateType::set_value ? "set value to " + to_string(update.values.value) : "delete") << endl; + cout << " grant: " << (update.update_grant == PermissionUpdateType::do_nothing ? "do nothing" : update.update_grant == PermissionUpdateType::set_value ? "set value to " + to_string(update.values.grant) : "delete") << endl; + } +} + +int main() { + ts::permission::setup_permission_resolve(); + /* + * +Structure size of PermissionManager: 176 +Structure size of PermissionContainerBulk<16>: 192 +Structure size of PermissionContainer: 12 + */ + cout << "Structure size of PermissionManager: " << sizeof(PermissionManager) << endl; + cout << "Structure size of PermissionContainerBulk<16>: " << sizeof(PermissionContainerBulk<16>) << endl; + cout << "Structure size of PermissionContainer: " << sizeof(PermissionContainer) << endl; + cout << "Permissions/bulk: " << PermissionManager::PERMISSIONS_BULK_ENTRY_COUNT << ". Bulks: " << PermissionManager::BULK_COUNT << " (Max permissions: " << (PermissionManager::PERMISSIONS_BULK_ENTRY_COUNT * PermissionManager::BULK_COUNT) << "; Avl: " << (uint32_t) PermissionType::permission_id_max << ")" << endl; + + PermissionManager manager{}; + print_permissions(manager); + manager.set_permission(PermissionType::b_client_ban_ip, {1, 0}, PermissionUpdateType::set_value, PermissionUpdateType::do_nothing); + manager.set_channel_permission(PermissionType::b_client_ban_ip, 2, {1, 0}, PermissionUpdateType::set_value, PermissionUpdateType::do_nothing); + manager.set_channel_permission(PermissionType::b_client_ban_ip, 2, {1, 0}, PermissionUpdateType::delete_value, PermissionUpdateType::do_nothing); + print_updates(manager); + //manager.set_permission(PermissionType::b_client_ban_ip, {1, 0}, PermissionUpdateType::delete_value, PermissionUpdateType::do_nothing); + //manager.cleanup(); + print_permissions(manager); + return 0; +} \ No newline at end of file