From ceb77a2c8562be37a33a2174edebfbdd421e9bba Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Sun, 21 Mar 2021 23:51:37 +0100 Subject: [PATCH] SOme updates --- server/main.cpp | 2 +- server/src/Configuration.cpp | 14 +++++------ server/src/channel/ServerChannel.cpp | 4 ++-- server/src/client/ConnectedClient.cpp | 3 ++- server/src/client/ConnectedClient.h | 2 ++ .../client/ConnectedClientNotifyHandler.cpp | 4 ++++ .../ConnectedClientTextCommandHandler.cpp | 2 +- server/src/client/SpeakingClient.cpp | 9 +++++-- server/src/client/SpeakingClient.h | 2 +- .../client/command_handler/bulk_parsers.cpp | 1 + server/src/client/command_handler/misc.cpp | 1 + server/src/client/command_handler/server.cpp | 4 ++-- server/src/client/query/QueryClientNotify.cpp | 24 +++++++++++++++++-- server/src/client/voice/CryptSetupHandler.cpp | 7 +++--- server/src/client/voice/VoiceClient.cpp | 19 ++++++++++----- server/src/client/voice/VoiceClient.h | 1 + .../voice/VoiceClientCommandHandler.cpp | 8 +++++-- server/src/server/voice/UDPVoiceServer.h | 2 +- server/tomcryptTest.cpp | 2 ++ shared | 2 +- 20 files changed, 80 insertions(+), 33 deletions(-) diff --git a/server/main.cpp b/server/main.cpp index 4cc3072..4618974 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -486,7 +486,7 @@ int main(int argc, char** argv) { logCriticalFmt(true, LOG_GENERAL, "Could not initialize SQL!"); if(errorMessage.find("database is locked") != string::npos) { logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------"); - logCriticalFmt(true, LOG_GENERAL, "{:^69}", "You're database is already in use!"); + logCriticalFmt(true, LOG_GENERAL, "{:^69}", "Your database is already in use!"); logCriticalFmt(true, LOG_GENERAL, "{:^69}", "Stop the other instance first!"); logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------"); } else { diff --git a/server/src/Configuration.cpp b/server/src/Configuration.cpp index 8720f45..1488159 100644 --- a/server/src/Configuration.cpp +++ b/server/src/Configuration.cpp @@ -1534,45 +1534,45 @@ std::deque> config::create_bindings() { } { - CREATE_BINDING("webrtc.port_min", 0); + CREATE_BINDING("webrtc.port_min", FLAG_RELOADABLE); BIND_INTEGRAL(config::web::webrtc_port_min, 50000, 0, 65535); ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in"); ADD_DESCRIPTION("A port of zero stands for no limit"); ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)"); } { - CREATE_BINDING("webrtc.port_max", 0); + CREATE_BINDING("webrtc.port_max", FLAG_RELOADABLE); BIND_INTEGRAL(config::web::webrtc_port_max, 56000, 0, 65535); ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in"); ADD_DESCRIPTION("A port of zero stands for no limit"); ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)"); } { - CREATE_BINDING("webrtc.stun.enabled", 0); + CREATE_BINDING("webrtc.stun.enabled", FLAG_RELOADABLE); BIND_INTEGRAL(config::web::stun_enabled, true, false, true); ADD_DESCRIPTION("Whatever to use a STUN server"); ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("webrtc.stun.host", 0); + CREATE_BINDING("webrtc.stun.host", FLAG_RELOADABLE); BIND_STRING(config::web::stun_host, "stun.l.google.com"); ADD_DESCRIPTION("The address of the stun server to use."); ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("webrtc.stun.port", 0); + CREATE_BINDING("webrtc.stun.port", FLAG_RELOADABLE); BIND_INTEGRAL(config::web::stun_port, 19302, 1, 0xFFFF); ADD_DESCRIPTION("Port of the stun server"); ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("webrtc.udp", 0); + CREATE_BINDING("webrtc.udp", FLAG_RELOADABLE); BIND_INTEGRAL(config::web::udp_enabled, true, false, true); ADD_DESCRIPTION("Enable UDP for theweb client"); ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("webrtc.tcp", 0); + CREATE_BINDING("webrtc.tcp", FLAG_RELOADABLE); BIND_INTEGRAL(config::web::tcp_enabled, true, false, true); ADD_DESCRIPTION("Enable TCP for theweb client"); ADD_NOTE_RELOADABLE(); diff --git a/server/src/channel/ServerChannel.cpp b/server/src/channel/ServerChannel.cpp index b5f0828..e8a5816 100644 --- a/server/src/channel/ServerChannel.cpp +++ b/server/src/channel/ServerChannel.cpp @@ -484,9 +484,9 @@ bool ServerChannelTree::validateChannelNames() { } bool ServerChannelTree::validateChannelIcons() { +#if 0 for(const auto &channel : this->channels()) { auto iconId = (IconId) channel->properties()[property::CHANNEL_ICON_ID]; -#if 0 if(iconId != 0 && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) { logMessage(this->getServerId(), "[FILE] Missing channel icon (" + to_string(iconId) + ")."); if(config::server::delete_missing_icon_permissions) { @@ -494,8 +494,8 @@ bool ServerChannelTree::validateChannelIcons() { channel->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing); } } -#endif } +#endif return true; } diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 5e90d2b..4fc7c9d 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -384,7 +384,8 @@ std::deque> ConnectedClient::unsubscribeChannel(co } bool ConnectedClient::isClientVisible(const std::shared_ptr& client, bool lock) { - for(const auto& entry : this->getVisibleClients(lock)) + auto client_list = this->getVisibleClients(lock); + for(const auto& entry : client_list) if(entry.lock() == client) return true; return false; diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index 2eac84f..cb9dd72 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "music/Song.h" #include "../channel/ClientChannelView.h" #include "DataClient.h" @@ -125,6 +126,7 @@ namespace ts { if(lock_channel) { lock.lock(); } + assert(mutex_shared_locked(this->channel_lock)); return this->visibleClients; } diff --git a/server/src/client/ConnectedClientNotifyHandler.cpp b/server/src/client/ConnectedClientNotifyHandler.cpp index 88d7637..7c67802 100644 --- a/server/src/client/ConnectedClientNotifyHandler.cpp +++ b/server/src/client/ConnectedClientNotifyHandler.cpp @@ -442,6 +442,8 @@ bool ConnectedClient::notifyClientMoved(const shared_ptr &clien assert(client->getClientId() > 0); assert(client->currentChannel); assert(target_channel); + sassert(mutex_shared_locked(this->channel_lock)); + sassert(mutex_shared_locked(client->channel_lock)); assert(this->isClientVisible(client, false) || &*client == this); Command mv("notifyclientmoved"); @@ -465,6 +467,7 @@ bool ConnectedClient::notifyClientUpdated(const std::shared_ptr channel_lock.lock(); } + sassert(mutex_shared_locked(this->channel_lock)); if(!this->isClientVisible(client, false) && client != this) return false; @@ -627,6 +630,7 @@ bool ConnectedClient::notifyClientEnterView(const std::shared_ptrgetClientId() > 0); sassert(to); sassert(!lock_channel_tree); /* we don't support locking */ + sassert(mutex_locked(this->channel_lock)); sassert(!this->isClientVisible(client, false) || &*client == this); switch (reasonId) { diff --git a/server/src/client/ConnectedClientTextCommandHandler.cpp b/server/src/client/ConnectedClientTextCommandHandler.cpp index 7c8a1f5..6732176 100644 --- a/server/src/client/ConnectedClientTextCommandHandler.cpp +++ b/server/src/client/ConnectedClientTextCommandHandler.cpp @@ -664,6 +664,7 @@ bool ConnectedClient::handle_text_command( TLEN(4); try { + /* auto type = stol(arguments[1]); auto generation = stol(arguments[2]); auto pid = stol(arguments[3]); @@ -671,7 +672,6 @@ bool ConnectedClient::handle_text_command( auto vc = dynamic_pointer_cast(this->ref()); if(!vc) return false; - /* auto& genestis = vc->getConnection()->get_incoming_generation_estimators(); if(type >= genestis.size()) { send_message(this->ref(), "Invalid type"); diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index 665042c..b39881e 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -403,7 +403,9 @@ void SpeakingClient::processJoin() { assert(ref_server); this->resetIdleTime(); - threads::MutexLock lock(this->command_lock); //Don't process any commands! + + /* don't process any commands */ + std::lock_guard command_lock_{this->command_lock}; if(this->state != ConnectionState::INIT_HIGH) { logError(this->getServerId(), "{} Invalid processJoin() connection state!", CLIENT_STR_LOG_PREFIX); @@ -507,7 +509,10 @@ void SpeakingClient::processJoin() { unique_lock server_channel_lock(this->server->channel_tree_lock); this->server->client_move(this->ref(), channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock); - if(this->getType() != ClientType::CLIENT_TEAMSPEAK) this->subscribeChannel({this->currentChannel}, false, true); /* su "improve" the TS3 clients join speed we send the channel clients a bit later, when the TS3 client gets his own client variables */ + if(this->getType() != ClientType::CLIENT_TEAMSPEAK) { + std::lock_guard own_channel_lock{this->channel_lock}; + this->subscribeChannel({this->currentChannel}, false, true); /* su "improve" the TS3 clients join speed we send the channel clients a bit later, when the TS3 client gets his own client variables */ + } } TIMING_STEP(timings, "join move "); diff --git a/server/src/client/SpeakingClient.h b/server/src/client/SpeakingClient.h index c8985d8..03c4714 100644 --- a/server/src/client/SpeakingClient.h +++ b/server/src/client/SpeakingClient.h @@ -79,7 +79,7 @@ namespace ts::server { virtual command_result handleCommandBroadcastVideoConfigure(Command &); void triggerVoiceEnd(); - inline void updateSpeak(bool onlyUpdate, const std::chrono::system_clock::time_point &time); + void updateSpeak(bool onlyUpdate, const std::chrono::system_clock::time_point &time); std::chrono::milliseconds speak_accuracy{1000}; std::mutex speak_mutex; diff --git a/server/src/client/command_handler/bulk_parsers.cpp b/server/src/client/command_handler/bulk_parsers.cpp index 485893a..000019b 100644 --- a/server/src/client/command_handler/bulk_parsers.cpp +++ b/server/src/client/command_handler/bulk_parsers.cpp @@ -2,4 +2,5 @@ // Created by WolverinDEV on 07/05/2020. // +#include #include "bulk_parsers.h" diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp index 1275728..8fdafd9 100644 --- a/server/src/client/command_handler/misc.cpp +++ b/server/src/client/command_handler/misc.cpp @@ -3391,6 +3391,7 @@ command_result ConnectedClient::handleCommandListFeatureSupport(ts::Command &cmd REGISTER_FEATURE("whisper-echo", FeatureSupportMode::FULL, 1); REGISTER_FEATURE("video", FeatureSupportMode::EXPERIMENTAL, 1); REGISTER_FEATURE("sidebar-mode", FeatureSupportMode::FULL, 1); + REGISTER_FEATURE("token", FeatureSupportMode::FULL, 1); this->sendCommand(notify); return command_result{error::ok}; diff --git a/server/src/client/command_handler/server.cpp b/server/src/client/command_handler/server.cpp index e2c61b2..ece708b 100644 --- a/server/src/client/command_handler/server.cpp +++ b/server/src/client/command_handler/server.cpp @@ -494,7 +494,7 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd) ); } - return command_result{error::ok}; + return ts::command_result{std::move(result)}; } command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd) { @@ -618,7 +618,7 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd) ); } - return command_result{error::ok}; + return ts::command_result{std::move(result)}; } command_result ConnectedClient::handleCommandServerGroupPermList(Command &cmd) { diff --git a/server/src/client/query/QueryClientNotify.cpp b/server/src/client/query/QueryClientNotify.cpp index 2f692eb..e7bd58c 100644 --- a/server/src/client/query/QueryClientNotify.cpp +++ b/server/src/client/query/QueryClientNotify.cpp @@ -155,6 +155,11 @@ bool QueryClient::notifyClientMoved(const std::shared_ptr &clie bool QueryClient::notifyClientLeftView(const std::shared_ptr &client, const std::shared_ptr &target_channel, ViewReasonId reasonId, const std::string &reasonMessage, std::shared_ptr invoker, bool lock_channel_tree) { if(!this->eventActive(QueryEventGroup::QEVENTGROUP_CLIENT_VIEW, QueryEventSpecifier::QEVENTSPECIFIER_CLIENT_VIEW_LEAVE)) { + std::unique_lock tree_lock{this->channel_lock, std::defer_lock}; + if(lock_channel_tree) { + tree_lock.lock(); + } + this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&, client](const weak_ptr& weak) { auto c = weak.lock(); if(!c) { @@ -168,8 +173,13 @@ bool QueryClient::notifyClientLeftView(const std::shared_ptr &c return ConnectedClient::notifyClientLeftView(client, target_channel, reasonId, reasonMessage, invoker, lock_channel_tree); } -bool QueryClient::notifyClientLeftView(const std::deque> &clients, const std::string &string, bool b, const ViewReasonServerLeftT &t) { +bool QueryClient::notifyClientLeftView(const std::deque> &clients, const std::string &string, bool lock_channel_tree, const ViewReasonServerLeftT &t) { if(!this->eventActive(QueryEventGroup::QEVENTGROUP_CLIENT_VIEW, QueryEventSpecifier::QEVENTSPECIFIER_CLIENT_VIEW_LEAVE)) { + std::unique_lock tree_lock{this->channel_lock, std::defer_lock}; + if(lock_channel_tree) { + tree_lock.lock(); + } + this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&](const weak_ptr& weak) { auto c = weak.lock(); if(!c) { @@ -180,11 +190,16 @@ bool QueryClient::notifyClientLeftView(const std::dequevisibleClients.end()); return true; } - return ConnectedClient::notifyClientLeftView(clients, string, b, t); + return ConnectedClient::notifyClientLeftView(clients, string, lock_channel_tree, t); } bool QueryClient::notifyClientLeftViewKicked(const std::shared_ptr &client, const std::shared_ptr &target_channel, const std::string &message, std::shared_ptr invoker, bool lock_channel_tree) { if(!this->eventActive(QueryEventGroup::QEVENTGROUP_CLIENT_VIEW, QueryEventSpecifier::QEVENTSPECIFIER_CLIENT_VIEW_LEAVE)) { + std::unique_lock tree_lock{this->channel_lock, std::defer_lock}; + if(lock_channel_tree) { + tree_lock.lock(); + } + this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&, client](const weak_ptr& weak) { auto c = weak.lock(); if(!c) { @@ -200,6 +215,11 @@ bool QueryClient::notifyClientLeftViewKicked(const std::shared_ptr &client, const std::string &message, std::shared_ptr invoker, size_t length, bool lock_channel_tree) { if(!this->eventActive(QueryEventGroup::QEVENTGROUP_CLIENT_VIEW, QueryEventSpecifier::QEVENTSPECIFIER_CLIENT_VIEW_LEAVE)) { + std::unique_lock tree_lock{this->channel_lock, std::defer_lock}; + if(lock_channel_tree) { + tree_lock.lock(); + } + this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&, client](const weak_ptr& weak) { auto c = weak.lock(); if(!c) { diff --git a/server/src/client/voice/CryptSetupHandler.cpp b/server/src/client/voice/CryptSetupHandler.cpp index 1b5a087..42db285 100644 --- a/server/src/client/voice/CryptSetupHandler.cpp +++ b/server/src/client/voice/CryptSetupHandler.cpp @@ -24,11 +24,11 @@ CryptSetupHandler::CryptSetupHandler(VoiceClientConnection *connection) : connec CryptSetupHandler::CommandHandleResult CryptSetupHandler::handle_command(const std::string_view &payload) { std::variant(CryptSetupHandler::*command_handler)(const ts::command_parser&) = nullptr; - if(payload.starts_with("clientinitiv")) { + if(payload.starts_with("clientinitiv ")) { command_handler = &CryptSetupHandler::handleCommandClientInitIv; - } else if(payload.starts_with("clientek")) { + } else if(payload.starts_with("clientek ")) { command_handler = &CryptSetupHandler::handleCommandClientEk; - } else if(payload.starts_with("clientinit")) { + } else if(payload.starts_with("clientinit ")) { command_handler = &CryptSetupHandler::handleCommandClientInit; } @@ -262,7 +262,6 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientEk(const auto pflags = protocol::PacketFlag::NewProtocol; this->connection->send_packet(protocol::PacketType::ACK, (protocol::PacketFlags) pflags, buffer, 2); - //Send the encrypted acknowledge (most the times the second packet; If not we're going into the resend loop) //We cant use the send_packet_acknowledge function since it sends the acknowledge unencrypted } diff --git a/server/src/client/voice/VoiceClient.cpp b/server/src/client/voice/VoiceClient.cpp index d9cae7f..020f722 100644 --- a/server/src/client/voice/VoiceClient.cpp +++ b/server/src/client/voice/VoiceClient.cpp @@ -51,9 +51,12 @@ VoiceClient::~VoiceClient() { delete this->connection; this->connection = nullptr; - if(this->flushing_thread) { - logCritical(this->getServerId(), "Deleting a VoiceClient which should still be hold within the flush thread!"); - this->flushing_thread->detach(); + { + std::lock_guard fthread_lock{this->flush_thread_mutex}; + if(this->flushing_thread) { + logCritical(this->getServerId(), "Deleting a VoiceClient which should still be hold within the flush thread!"); + this->flushing_thread->detach(); + } } memtrack::freed(this); @@ -194,13 +197,15 @@ bool VoiceClient::close_connection(const system_clock::time_point &timeout) { } debugMessage(this->getServerId(), "{} Closing voice client connection. (Flush: {})", CLIENT_STR_LOG_PREFIX, flush); + + std::lock_guard fthread_lock{this->flush_thread_mutex}; //TODO: Move this out into a thread pool? if(this->flushing_thread && this->flushing_thread->joinable()) { logCritical(LOG_GENERAL, "VoiceClient::close_connection reached flushing thread with an active old handle. Ignoring request."); return true; } - this->flushing_thread = std::make_shared([this, self_lock, timeout, flush]{ + auto flush_thread = std::make_shared([this, self_lock, timeout, flush]{ { /* Await that all commands have been processed. It does not make sense to unregister the client while command handling. */ std::lock_guard cmd_lock{this->command_lock}; @@ -235,7 +240,8 @@ bool VoiceClient::close_connection(const system_clock::time_point &timeout) { this->finalDisconnect(); }); - threads::name(*this->flushing_thread, "Flush thread VC"); + this->flushing_thread = flush_thread; + threads::name(*flush_thread, "Flush thread VC"); return true; } @@ -255,10 +261,11 @@ void VoiceClient::finalDisconnect() { //Unload manager cache this->processLeave(); { + std::lock_guard fthread_lock{this->flush_thread_mutex}; if(this->flushing_thread) { this->flushing_thread->detach(); //The thread itself should be already done or executing this method } - this->flushing_thread.reset(); + this->flushing_thread = nullptr; } if(this->voice_server) { this->voice_server->unregisterConnection(ownLock); diff --git a/server/src/client/voice/VoiceClient.h b/server/src/client/voice/VoiceClient.h index 4753928..d448165 100644 --- a/server/src/client/voice/VoiceClient.h +++ b/server/src/client/voice/VoiceClient.h @@ -115,6 +115,7 @@ namespace ts { command_result handleCommandClientDisconnect(Command&); //Locked by finalDisconnect, disconnect and close connection + std::mutex flush_thread_mutex{}; std::shared_ptr flushing_thread; std::unique_ptr server_command_queue_{}; diff --git a/server/src/client/voice/VoiceClientCommandHandler.cpp b/server/src/client/voice/VoiceClientCommandHandler.cpp index 69680ba..bc31634 100644 --- a/server/src/client/voice/VoiceClientCommandHandler.cpp +++ b/server/src/client/voice/VoiceClientCommandHandler.cpp @@ -109,9 +109,13 @@ command_result VoiceClient::handleCommandClientInit(Command &cmd) { command_result VoiceClient::handleCommandClientDisconnect(Command& cmd) { auto reason = cmd["reasonmsg"].size() > 0 ? cmd["reasonmsg"].as() : ""; - this->notifyClientLeftView(this->ref(), nullptr, VREASON_SERVER_LEFT, reason, nullptr, false); //Before we're moving us out of the channel tree! + { + std::shared_lock own_lock{this->channel_lock}; + this->notifyClientLeftView(this->ref(), nullptr, VREASON_SERVER_LEFT, reason, nullptr, false); //Before we're moving us out of the channel tree! + } + if(this->state == CONNECTED) { - unique_lock channel_lock(this->server->channel_tree_lock); + std::unique_lock channel_lock{this->server->channel_tree_lock}; this->server->client_move(this->ref(), nullptr, nullptr, reason, VREASON_SERVER_LEFT, true, channel_lock); } logMessage(this->getServerId(), "{} Got remote disconnect with the reason '{}'", CLIENT_STR_LOG_PREFIX, reason); diff --git a/server/src/server/voice/UDPVoiceServer.h b/server/src/server/voice/UDPVoiceServer.h index 358c879..11c5cb5 100644 --- a/server/src/server/voice/UDPVoiceServer.h +++ b/server/src/server/voice/UDPVoiceServer.h @@ -59,7 +59,7 @@ namespace ts::server::server::udp { }; struct ServerEventLoops { - event_base* event_base{nullptr}; + struct event_base* event_base{nullptr}; std::thread dispatch_thread{}; }; diff --git a/server/tomcryptTest.cpp b/server/tomcryptTest.cpp index e11a21b..1e38a08 100644 --- a/server/tomcryptTest.cpp +++ b/server/tomcryptTest.cpp @@ -28,6 +28,7 @@ void testTomMath(){ #else //assert(err != MP_OKAY); //if this method succeed than tommath failed. Unknown why but it is so #endif + (void) err; mp_clear_multi(&x, &n, &exp, &r, nullptr); } { @@ -53,6 +54,7 @@ void testTomMath(){ #else //assert(err != MP_OKAY); //if this method succeed than tommath failed. Unknown why but it is so #endif + (void) err; mp_clear_multi(&x, &n, &exp, &r, nullptr); } diff --git a/shared b/shared index eef0144..8dde5b1 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit eef0144e77ee87fc7fe275beb743bd85e6e37f19 +Subproject commit 8dde5b1c23f0d84ca1b56a8c80389fb4e887b062