diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp index 244dbc1e317702e4299ff4f0f7337ceb31067cf8..ef74f915a251d3ec40fd74b228a240e1a6993988 100644 --- a/src/jamidht/conversation_module.cpp +++ b/src/jamidht/conversation_module.cpp @@ -757,6 +757,9 @@ ConversationModule::ConversationModule(std::weak_ptr<JamiAccount>&& account, void ConversationModule::loadConversations() { + auto acc = pimpl_->account_.lock(); + if (!acc) + return; JAMI_INFO("[Account %s] Start loading conversations…", pimpl_->accountId_.c_str()); auto conversationsRepositories = fileutils::readDirectory( fileutils::get_data_dir() + DIR_SEPARATOR_STR + pimpl_->accountId_ + DIR_SEPARATOR_STR @@ -790,17 +793,24 @@ ConversationModule::loadConversations() // Prune any invalid conversations without members and // set the removed flag if needed size_t oldConvInfosSize = pimpl_->convInfos_.size(); + std::set<std::string> removed; for (auto itInfo = pimpl_->convInfos_.cbegin(); itInfo != pimpl_->convInfos_.cend();) { const auto& info = itInfo->second; if (info.members.empty()) { itInfo = pimpl_->convInfos_.erase(itInfo); continue; } + if (info.removed) + removed.insert(info.id); auto itConv = pimpl_->conversations_.find(info.id); if (itConv != pimpl_->conversations_.end() && info.removed) itConv->second->setRemovingFlag(); ++itInfo; } + // On oldest version, removeConversation didn't update "appdata/contacts" + // causing a potential incorrect state between "appdata/contacts" and "appdata/convInfos" + if (!removed.empty()) + acc->unlinkConversations(removed); // Save iff we've removed some invalid entries if (oldConvInfosSize != pimpl_->convInfos_.size()) pimpl_->saveConvInfos(); diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index e429f1ff582921a46999282861647d0a4a3eba00..93887219b4aacd0b2f5d63bfff8ddeddf2f1111d 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -1042,6 +1042,26 @@ JamiAccount::forceReloadAccount() loadAccount(); } +void +JamiAccount::unlinkConversations(const std::set<std::string>& removed) +{ + std::lock_guard<std::recursive_mutex> lock(configurationMutex_); + if (auto info = accountManager_->getInfo()) { + auto contacts = info->contacts->getContacts(); + for (auto& [id, c] : contacts) { + if (removed.find(c.conversationId) != removed.end()) { + JAMI_WARN( + "[Account %s] Detected removed conversation (%s) in contact details for %s", + getAccountID().c_str(), + c.conversationId.c_str(), + id.to_c_str()); + c.conversationId.clear(); + } + } + info->contacts->setContacts(contacts); + } +} + bool JamiAccount::revokeDevice(const std::string& password, const std::string& device) { diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h index be67a8374bad6a6470298530062624d80f946116..a24f77e9c512e7a9305416a762d6c0504fa4240e 100644 --- a/src/jamidht/jamiaccount.h +++ b/src/jamidht/jamiaccount.h @@ -85,7 +85,6 @@ class IceTransport; struct Contact; struct AccountArchive; class DhtPeerConnector; -class ContactList; class AccountManager; struct AccountInfo; class SipTransport; @@ -599,6 +598,12 @@ public: */ void forceReloadAccount(); + /** + * Make sure appdata/contacts.yml contains correct informations + * @param removedConv The current removed conversations + */ + void unlinkConversations(const std::set<std::string>& removedConv); + private: NON_COPYABLE(JamiAccount); diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp index 4d475ee22fa4889bea91167392e4d8d3e4149fa5..570afa16612920dac5a15f36ebc730b85bf071bf 100644 --- a/test/unitTest/conversation/conversation.cpp +++ b/test/unitTest/conversation/conversation.cpp @@ -28,14 +28,15 @@ #include <filesystem> #include <msgpack.hpp> -#include "manager.h" #include "../../test_runner.h" -#include "jami.h" -#include "base64.h" -#include "fileutils.h" #include "account_const.h" -#include "conversation/conversationcommon.h" +#include "archiver.h" +#include "base64.h" #include "common.h" +#include "conversation/conversationcommon.h" +#include "fileutils.h" +#include "jami.h" +#include "manager.h" using namespace std::string_literals; using namespace DRing::Account; @@ -107,6 +108,7 @@ private: void testCountInteractions(); void testReplayConversation(); void testSyncWithoutPinnedCert(); + void testImportMalformedContacts(); CPPUNIT_TEST_SUITE(ConversationTest); CPPUNIT_TEST(testCreateConversation); @@ -138,6 +140,7 @@ private: CPPUNIT_TEST(testCountInteractions); CPPUNIT_TEST(testReplayConversation); CPPUNIT_TEST(testSyncWithoutPinnedCert); + CPPUNIT_TEST(testImportMalformedContacts); CPPUNIT_TEST_SUITE_END(); }; @@ -2474,6 +2477,29 @@ ConversationTest::testSyncWithoutPinnedCert() CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); } +void +ConversationTest::testImportMalformedContacts() +{ + auto malformedContacts = fileutils::loadFile(std::filesystem::current_path().string() + + "/conversation/rsc/incorrectContacts"); + auto bobArchive = std::filesystem::current_path().string() + "/bob.gz"; + std::remove(bobArchive.c_str()); + archiver::compressGzip(malformedContacts, bobArchive); + std::map<std::string, std::string> details = DRing::getAccountTemplate("RING"); + details[ConfProperties::TYPE] = "RING"; + details[ConfProperties::DISPLAYNAME] = "BOB2"; + details[ConfProperties::ALIAS] = "BOB2"; + details[ConfProperties::UPNP_ENABLED] = "true"; + details[ConfProperties::ARCHIVE_PASSWORD] = ""; + details[ConfProperties::ARCHIVE_PIN] = ""; + details[ConfProperties::ARCHIVE_PATH] = bobArchive; + bob2Id = Manager::instance().addAccount(details); + wait_for_announcement_of({bob2Id}); + auto contacts = DRing::getContacts(bob2Id); + CPPUNIT_ASSERT(contacts.size() == 1); + CPPUNIT_ASSERT(contacts[0][DRing::Account::TrustRequest::CONVERSATIONID] == ""); +} + } // namespace test } // namespace jami diff --git a/test/unitTest/conversation/rsc/incorrectContacts b/test/unitTest/conversation/rsc/incorrectContacts new file mode 100644 index 0000000000000000000000000000000000000000..bf284eceb19ca684a16d817ab39daa7a7fc74f1c --- /dev/null +++ b/test/unitTest/conversation/rsc/incorrectContacts @@ -0,0 +1,23 @@ +{ + "ethKey":"Yx1htDVByf6TdcpYt0khSwCSz8U6TcgRIMwioEubhb0=", + "ringAccountContacts": { + "0aaa0aaa0aaa0aaa0aaa0aaa0aaa0aaa0aaa0aaa": { + "added": 1638979587, + "confirmed": true, + "conversationId": "3d411a98c45b264de7da1186eba328caa65ea955" + } + }, + "conversations": { + "3d411a98c45b264de7da1186eba328caa65ea955": { + "created": 1643907807, + "erased": 1643918916, + "id": "3d411a98c45b264de7da1186eba328caa65ea955", + "lastDisplayed": "", + "members": [ + { + "uri": "0aaa0aaa0aaa0aaa0aaa0aaa0aaa0aaa0aaa0aaa" + } + ], + "removed": 1643918916 + } + },"ringAccountCert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXVENDQTBHZ0F3SUJBZ0lJSk5oQk9CMjJ6Mjh3RFFZSktvWklodmNOQVFFTUJRQXdUREVRTUE0R0ExVUUKQXhNSFNtRnRhU0JEUVRFNE1EWUdDZ21TSm9tVDhpeGtBUUVUS0RRMFpqSTFZV1F5TURKak16a3pZelF4TldKawpNVEpsT1RCbVlXVm1Zemd6TnpnME1UVXdPREF3SGhjTk1qSXdNakEwTWpFd01UVTBXaGNOTXpJd01qQXlNakV3Ck1UVTBXakJKTVEwd0N3WURWUVFERXdSS1lXMXBNVGd3TmdZS0NaSW1pWlB5TEdRQkFSTW9aRGN5TmpZek5qUmsKWWpVM056SXdZVFpsWWpJNE5qQTNZV00wTUdFeVpqZGxNREJqT1RVNVpUQ0NBaUl3RFFZSktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFLOWhHU05iaTk3RkJGeTI0d0dqWk1HRG9ST0ZkSDFEd2VRb2VXb2ZHbG1JCm1rVjVQcnM5OVA3cWFUNkhneGIrVXdTN2txYktUcW5zUVBqUU9BV0tEblNBeGozRzI5MDNSQnVGUjZLVTdBK3AKeGljckJUMStyOGorNy9JaUpqbGk5YmVwUEI0Uk13eDBjMEZNVzgvVm1HeXJ5SjFEV0FoSGVkbnFMQjhhZE9aSAo1M2JjZ2V3NVQ4ejNtK3I4dmE0ODNJSkExRllsZlpxMkpac0IvWkU2TUpYckpibzBMWWZCcVpSc3Z4UUhETnRoClBVSDlHR1FYaUtuSGo3MHk3ZzltdDNEY2FkY2Npakt3djNoS1lHa3krVE1IRm01Ym5ZTy8zMEROL1U3dHl3cWgKM0FtNXFVOElRY2tnc0oyN2NXaUhNcHQxSE92b1dYcW1iQjJadEI3V1dPWnVKTEZwSFdPQkFNa0tUWFVMM04wTAo4SHhML1ZGbGdaRzRuUHEvSURGbTdPeFYza1pISU9FMnlGWi9ncWx6OFBRUFlSbkRDWnNxZ2szYkNza2dheWlwCm9IWUYrb2ZEUyttdXcvVVdqSEhZSzR4cmgwVHRnaVNEa3hyMFdzR0VmbEJTVkd4dk16cStMQ3ArQ24xK21xMjcKU0hKRTlXbGlWZkkxaWUxM1NsVjFRbFlObGM0Mk9tUlVaOEtmbm5DNG9UcldNNTVmNmhoRXQ5M1kwUmpkWmpMMwpyUHhCYVhBWlRFYnRMQzV0c1B3T0pTemdRZFpJcWw3OSt2UmZuZ2ZHalQ1a1kwRnNldTZEVFByNFJzUlp0eEs2CkUvZVloNXFMYkx6SFM1bGEvbDAwS2h2YUVHVlZFT2NYaHFGd3ZiSnhmaUU3UFM3bkIrcURESU5RbVhBVmQrS0oKQWdNQkFBR2pRakJBTUIwR0ExVWREZ1FXQkJUWEptTmsyMWR5Q202eWhnZXNRS0wzNEF5Vm5qQVBCZ05WSFJNQgpBZjhFQlRBREFRSC9NQTRHQTFVZER3RUIvd1FFQXdJQkJqQU5CZ2txaGtpRzl3MEJBUXdGQUFPQ0FnRUFNTEg1CnZtQUxDMGRwOXJQWFJqSjZYazJqeXZxZ1lNTUJCNmFzN3VvVWE3dWQra2QyNmdRYjN4UDBzbTdoOEorUFBqcTEKU054YlZpQ01OMlIvSEtPV1JEV2xjM09zcmtGMXlOREo2b0Rjd1hhNk9Idld4bGpqdVpuYlBLVm9NRmV4UmtUOQo0RGdiQmNuc0VQRXRIbjBkcERGVmF6d0dFWm1jL1FNc0IxelBRWXlWTWxYVTF3bVpSZmppWSszbWpNR3YzOFd3Cnc3MmlGZzByWkdMVGZ2RVBINWhkVWl4QS9yaXFSb0dpaG1pbWMwZ2FkR2lJeEEwR1puYTZNU3hWN3NDYkRiVHcKRlhVS0IrTEFNbHVGQnF5S2tlb0tJTHM0YS9hZ3RpOGdOYm1rbldFTTVTR2tCN2p3ME5wWmpMSWlHUm1icUR5dApGS0NNVHB5Ym1oSzRMbC9lZkE2bW9Jd1J4NU9oR2J5QXE1YVdjV0ZnZUJtSVVnRUs2MFFlR08xZ2l2VVlFQmR1Cm9SU1V3OEZUUWgwbEJ2MkExOEFyZkdHN1ZEY2xxUi9vZXRwWmNNZ3FNa01tMExNRzNCNEpXUFZaVXFCNERncWQKOVN3UTNaMUNySUttNnU4b29ZUE9DaHBIakUreElxUkRDNFI4ZDdVbnp3Y0tia082UVhFQ09ycGtsK0pLd0dGbgpGU2xmRm1MWHg3bHdEM01CQVJGTlgxRGFsN2grODJOVDZyMnY5SHRrUnM3QlNxWURJMGlpY0VpVkFjbnRVeGhkCmR6OHpzSnpDU0FzaUVrWUF6VFpXZlVjbkFyYVlkUlNyMFQ5YlhBMEkvVm9XMFpFNFIvN0hLMm54QjRTM2l2Q1IKeFU0bFE5Z2g2VEZWd1VXVUlJbkFkanNvbG5wL0xpajNCQVVrck1rPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlGWERDQ0EwU2dBd0lCQWdJSTJSMDVoRkdFemdJd0RRWUpLb1pJaHZjTkFRRU1CUUF3VERFUU1BNEdBMVVFCkF4TUhTbUZ0YVNCRFFURTRNRFlHQ2dtU0pvbVQ4aXhrQVFFVEtEUTBaakkxWVdReU1ESmpNemt6WXpReE5XSmsKTVRKbE9UQm1ZV1ZtWXpnek56ZzBNVFV3T0RBd0hoY05Nakl3TWpBME1qRXdNVFUwV2hjTk16SXdNakF5TWpFdwpNVFUwV2pCTU1SQXdEZ1lEVlFRREV3ZEtZVzFwSUVOQk1UZ3dOZ1lLQ1pJbWlaUHlMR1FCQVJNb05EUm1NalZoClpESXdNbU16T1ROak5ERTFZbVF4TW1VNU1HWmhaV1pqT0RNM09EUXhOVEE0TURDQ0FpSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQUxjQjVXcmFaOTdFQWp3eTJxTVhudGlaMjlQVndNajdXR0VqUHNFWgpQcHM4UGdvZ3BkdWhkc3UxZG91N3BXNVFTc2xCc0hNM2hDU05UNTBXNEdxWFFiZUszVURoNHhhaUpUMkRid0xqCndTWWsvSmpBcjhBU3l2bXk1Q2JvT2FTcW92Q2h5K1JnY0RlSFZYM3NpSnNBeDVPZXBadXZ3VmdrN2paT2xQcUcKazVNbmxCL2l3ZHFjbmpiNnc1VStaUVZkd21DL25vL2hNNVMrYUxySzhiNkhHSjI3eXFrL0ZOYmNnQUhwWExKWAp6Z1RKRDJ5SWF2MUZaTzRhUDB1TjBad0ZIbndXcHM3U0R2TmcydlplVUNia0xOTG9EckplVlVmVEwwbG9CbFlxCnZTcTV1RE1oZHJ4WTZVQVI5Z3h4NnQrcUk0cTlhU1V5bmdDcm9PZG5xQXlJVmJwbUN5N2s0R2ZtMWRHcE10cGgKR094VVNTOHF5cVFWTU1UUFJtcXpYUjdwblRSaWcvNWtyMU42NXZjK2pMRWM0ZVZxcVZOOUh4Z0dSUHNJdzd3QQpqZzVIbE5yamRlUnltVFdwbDlVNWJkNzV0U1hTRjIvSFErMW9pMmZERXN2UThzbDJ3MzhDSm1Vak14RlBkMU5WCjNKKzFMV3JMN2krMFgwMnF6VklFblQ3c3BYZnRxekJPSXhRNTN0UFpjdU5SRnZ4dHF3eTc1cEdycm4ra3FWNjIKRFR0ZWFrNUNSNW1NcmJtZ2NaZjBNR25Idm1GVEUrd0tTR3pvb3VFRkNiODEveG9mYTVIZHZ0WnZFWUhmS3BRMwpuTS9hbndQdzRwek5aVG9sTjZvaEd0VFJtRUFNZDg4RTRPdE1STjdRSXN5Zk94anlWY21lSE0wZFhsRW1ENTZJCnh0SHZBZ01CQUFHalFqQkFNQjBHQTFVZERnUVdCQlJFOGxyU0FzT1R4Qlc5RXVrUHJ2eURlRUZRZ0RBUEJnTlYKSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCQmpBTkJna3Foa2lHOXcwQkFRd0ZBQU9DQWdFQQpZWC9UaEltU0Y2UHJLNGgvaVg4S0k0STJjSkdJTHVrbTh1N1VLWW1nMjRrTDBJaXdaczRYcUYxc0ppVXQ4ODErClVYRS96M2VPbnRVRWN1bHZaMlYrT1NPVHJtQzNTUVlGV3lCR2p2THBONitZNERWWlQ1dzRnbENqNU1BcWtZUXMKODg2YUkzNnVoMmRTeHJiUmNVR2N0MkhCNm1zaVQ5ZEJ4dUkzdWFXWFVjcHNhUnZ0L0NWVmVlKzVyeEdGWHNlbwo4Wk91ZHBib3VYbFlBeW5sS0E2ZFRjNXJ6VGxQVitHc3lZNHVXaittSURqNHJoTHd0UG9wVUFTbEZLOE56ZEJ5CmpqTmRLd2xTRVRMTE9kY0RYVm1hYnNrWXRPbW1DS3ZJeHVseHphRVI5YThMcG5hZ0JqVHBXM1RsWGhXa1BqVEUKem8wcVdjYkM4MGJOTU5pU3l1MFZ0cjgwQ3lWRExaYjVGUStLdlluN2NOWFQrVEdBWCtWem8vOXVqLy9YQlcrYQpyc2tNcTRDcVRObGdjaDUwSGJNbStrTlBKOHZQSmNpU2hTTC9IUmQzNlNWaVpUR0laYWw4ZThYRnFOUk9PL1FYCmIxb2pBU2NCUEJOZlQvZVpUbGRtSE1rTVRpZEk3czhNL0k4aEpPZnBVZUE1K3hpaWJ2RDlLNTg4aW1Yb3VhUUkKK1lPU0d1SWFLWnEzTjY1azR5SGFQeGc0SkJDMC9MSmxCT0xGQUxZNFUzdHJwa2VBNnI3ZVpiWXp0WnlWUC83MQp1dVNiNkMwbnVReVhIY014OXhQbHQ5L1djM3puNFFNRDZmNzgxTjd6cmFsQzVPVlBya1BEa2RIdW51OEJwejIxCmJNZE4rNWwvcmM1NmNWVG1kOUhaWVNSN3E0MWYzUmNjbkJiRkJoWWFQN3c9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","ringAccountKey":"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpQd0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Nrd2dna2xBZ0VBQW9JQ0FRQ3ZZUmtqVzR2ZXhRUmMKdHVNQm8yVEJnNkVUaFhSOVE4SGtLSGxxSHhwWmlKcEZlVDY3UGZUKzZtaytoNE1XL2xNRXU1S215azZwN0VENAowRGdGaWc1MGdNWTl4dHZkTjBRYmhVZWlsT3dQcWNZbkt3VTlmcS9JL3UveUlpWTVZdlczcVR3ZUVUTU1kSE5CClRGdlAxWmhzcThpZFExZ0lSM25aNml3ZkduVG1SK2QyM0lIc09VL005NXZxL0wydVBOeUNRTlJXSlgyYXRpV2IKQWYyUk9qQ1Y2eVc2TkMySHdhbVViTDhVQnd6YllUMUIvUmhrRjRpcHg0KzlNdTRQWnJkdzNHblhISW95c0w5NApTbUJwTXZrekJ4WnVXNTJEdjk5QXpmMU83Y3NLb2R3SnVhbFBDRUhKSUxDZHUzRm9oektiZFJ6cjZGbDZwbXdkCm1iUWUxbGptYmlTeGFSMWpnUURKQ2sxMUM5emRDL0I4Uy8xUlpZR1J1Sno2dnlBeFp1enNWZDVHUnlEaE5zaFcKZjRLcGMvRDBEMkVad3dtYktvSk4yd3JKSUdzb3FhQjJCZnFIdzB2cHJzUDFGb3h4MkN1TWE0ZEU3WUlrZzVNYQo5RnJCaEg1UVVsUnNiek02dml3cWZncDlmcHF0dTBoeVJQVnBZbFh5TlludGQwcFZkVUpXRFpYT05qcGtWR2ZDCm41NXd1S0U2MWpPZVgrb1lSTGZkMk5FWTNXWXk5Nno4UVdsd0dVeEc3U3d1YmJEOERpVXM0RUhXU0twZS9mcjAKWDU0SHhvMCtaR05CYkhydWcwejYrRWJFV2JjU3VoUDNtSWVhaTJ5OHgwdVpXdjVkTkNvYjJoQmxWUkRuRjRhaApjTDJ5Y1g0aE96MHU1d2ZxZ3d5RFVKbHdGWGZpaVFJREFRQUJBb0lDQUZOdGxVL0VsTno4Uy83dTRUNCtSQWZxClhnMnpScDd3UktRTXZQVkdwbnBCQ1dKVE13eFBoT1JmRG9HNFpSdmpFQXNJVDlNWUdpT3JSNTBWTUJFeWczM1kKWkhXdXk1aUlXZFhkcVI0bVNjV0p0Y0djTFhuN2NoUGpjckYwblVwZlVSUjFaU3ZJY3NoUi9TSHErU0g5TWUxQQpLc2JrNmxQRjdLbEZSSWZuUmdUbDM2NGpaNHJBR0w0Z2JBTjFCNmFEUFVLWkpHbDJuREdreEc3Z200Y2liMHBoCitpaUIrY2JDMGJEUnFuUVVob0UxMkxZemVBN2xReFhBNFJmWHluZDVIWHFLMzY1ODNTRVFBNFZ0bkQ5dkNDekcKQ2wybEoyRXRQVDdPdnRsMU5VbHhoZ0w2VnhoSmpVSG9VeHcxdHRWS3UwQklqZUdMUGtXaU5Ydy9TZmdCYjNnawo2K1NEbTA4VEJSVFpEd3JsQVRDMS8vSHhIMjlFTnpUWVRkSVRVbTFqY3F3OFFydmQrVlBBTHpQRUJ4czQ0b3RVCm00c3NabU9ib2xjR0NmcWhyd2F2alpzZ3dmUXptWjNTZW5QUkpaNTVLdXpkMUlRYjhYREhYOXR2azJCS2VKMWwKNGlmcVd2Z3B4Nzg0RkxReVZWRW1NbkhSTktxbTFuOWhoOVNvUmtjRVAxSFFvUmZXckZzcEFsS28xWmJqTlhZeAphbnRZTGVnd3lNcVJsZzZWanNHK1lGUTVIZkdNenpBZmFqNjdhZnBpNnF6UXR4N1lyRGIzMFA4YXBjbjlsM1lCClAvZTdteGdFTEwyVjZFTjNENlVuMFhFSVRMMlIrNWFrYVd2Q0lVeFNIejNCejdjRll5NmNQaTc1R291YWhsMTAKbFpZMkRRSEYwTHZ4S1BYNWJGK2hBb0lCQVFETlk0eGZGY1p1eWFidm9JZFhDTmlkcmV3WnMwYVhtclZWeHA1NAorU29ycHE1MllUN1NQT1o5Z1hPOWlKa0sydFRlQit0Z2IrNEFtSElNTTI4aWlHay90bmc4VUFtNmM1c2xrUXhPCm1icWYwS3pBZlU4enc0MzN6OHR2NUxVNVRnWXdqQUJpSTZTd21KTmdvVUxsMzVuT01JU2tOOFRkMmo4aXNkWjkKRkVnWEJpL3RFdTBwaVBNc3U1ajAxYXlCZkdMaEhsUUxFZFVJOWRTTGZVbEI5d0xKWVNDRHRIbWp4VXhZbFk2VgpQREdKakhRSjg5L0tpaGZSQkhPUmFEalFteEJZSmdWeHhDWEExN2NvTHQyRU1KaXpucGMzQUlRRDl6NE9mc3lQCnozSnBDMktlRmJFSStkdEVPREUrSDdKY2lZU0N4SDVaVjFDb0FyMU9qQzI0UkZ2SEFvSUJBUURhbUhoR1JUWFIKVi9ybTBCdFVLWkVFYk85UEhkUWVjOEswWjRUdGYyQ251d3M3eXlLbHFCN0dwMXF2R1lsWlJla2l0Vmttbkc5MgpkZ1hJSWVmcnpwUE9Zd24wUWhGVnlhVll0MXVEdjV4Y3ZKSzhzdFpCYXV5SGphN24vbHZ0OXdIekE2RFVlZzB0CnMwK2sxYmRhVDdXUWNGbFZBc2pBSHllY05pekpQekJ0T2U1cnhSZWtKTGMwTExEbWJoMUpYdFowcG8rZE9nSTEKby96ZDZNeEsrK2UvSVJKaWNZcVNLYWdFZ2VHT2FuN2Q1VUZBS3NGN05lUzhrSi9aQWxWNEluL1Q5VXRCa20xUwpyWHQvWUVoVGJqT3NpY3l4S2dWRFF2a2E4VksxcVIyOTg5UDJUU1B1VklBcmZhVGpZWFd4blJ4M2FXMGZ3dTR1Ckx0L3d6T044WXE4dkFvSUJBQmJMQ21QdS9iT0R5M1BpWThLajU2NHdBL2lpTFo1YmUrNUYrWVpCV0kyNWxManMKZDlWTER1VTB2WXgySVUzVUZUV0N1WHRkVUpOZVY2SEUvM3RjbWhtSS9YVHNDUGhaSmdtWVBENW96dzQzVVVTWApZM2ZDSnZvdVpFeEpuRmI4c3JFbWlGQmIxa1lHMkt3cm1VbjdjNDB2VXprS08xM0hmNlF4Q2pXZFQ1YkdpQUVPClRhV0RYa0ZEbkdqbllacUpIRXdSV3BCVDdVbHVScVVvUlQzOTFLQldtQTRsdEM0WmtpeHVoSUZKU2RJUzlUbTUKaDhkcFpkVTdZanZyemdwUWV4TGRQVk52eWlUSmk3ajQ0bnB1RGpRNDBGMDVRRzFVUGQzTDhwV3ZGdXBUYWlGVApGTytERXZOU0xSelExYkZ4VFhSWnJBL3NJYlpUZjRDOUM4SGNURGNDZ2dFQUlZN1lvQWtOK3daanFUMTNoSk1oCkJTbm9EeUJJS1NpaVVtbTErWFBkbGNKY05pQzRQL0F3VVZUdDBnVzhsU3pyWG4yeW92L1BDTU9UbmFhKzRPK0UKdDJGeTdNR2J3YXpFS250em5DVTFUdk5RSDVIZzNJZ2FxYkN4a2tIMVFQSHpwYWYvNzc2UStWd1d0UHB1UEhvUwpLSXRtMjJzakFJT0g1QzVZNzhnNG1md1R3dzJ0MUhBaWcrcjhlVk9jUVdCQzBnT2o5b0lxUnF2WExRWUpIdFhQClBRTkp2RDBzQmNaSldlL0tmYitSbkdVT2dGaFAvYWRIWno5WW1zc0Y3ZngxM1VkQjkxNFFWR24zbEpWbVpSQjgKOUZidlNwOWwwVzZkcVdvMHhWaWlEVUE2WVF4MG9LanVTRDd3cWt1Uzh1Sm90VmhFL1dSQ2htcUJ6SWpYdjRleQoxd0tCLzMvYnJmbzBpNmNpRTBIWnNsZU9HZEE1Ymt4TU15UlZRWFIwNFF1b1VrcndqcVFnU0N3ZzF4T0FYaTFkCjE5TGRDaldodFc5TEtSUVhhZUp3bkNNaWR3bUd0TUErSG5EOWxCekVkb0hETjkwek5qV09zcUVDbFdaTE1KR3gKeTlJUzYySnk1TVArK2JtdVd2WXI2YmtTa0NqUmdnQjhmMk9wb1BzUlMyY3RUaTN3ZHFFWnQwblphbDAvM2hpVwprUGpIVUtFK09jSUtwdk1yUGpQVjZmVEprUUI3OHl4L29vL0RNbVFXc1g4aUQzY3dtV3hLWDc3MDNHRExpNC8wCmk1cmZ5UVM2Q29CZU96cmZMZzFiOVZlK01UcWs4N0dzUmdzVHdMNVRhTW1CQ3BBb2xvN050Tm40T3BmcjA5MW8KZEZ2ZmtFMWM3a0JlNU5SYitoWkp0bytoZ1E9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==","ringCaKey":"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQzNBZVZxMm1mZXhBSTgKTXRxakY1N1ltZHZUMWNESSsxaGhJejdCR1Q2YlBENEtJS1hib1hiTHRYYUx1NlZ1VUVySlFiQnpONFFralUrZApGdUJxbDBHM2l0MUE0ZU1Xb2lVOWcyOEM0OEVtSlB5WXdLL0FFc3I1c3VRbTZEbWtxcUx3b2N2a1lIQTNoMVY5CjdJaWJBTWVUbnFXYnI4RllKTzQyVHBUNmhwT1RKNVFmNHNIYW5KNDIrc09WUG1VRlhjSmd2NTZQNFRPVXZtaTYKeXZHK2h4aWR1OHFwUHhUVzNJQUI2Vnl5Vjg0RXlROXNpR3I5UldUdUdqOUxqZEdjQlI1OEZxYk8wZzd6WU5yMgpYbEFtNUN6UzZBNnlYbFZIMHk5SmFBWldLcjBxdWJneklYYThXT2xBRWZZTWNlcmZxaU9LdldrbE1wNEFxNkRuClo2Z01pRlc2WmdzdTVPQm41dFhScVRMYVlSanNWRWt2S3Nxa0ZUREV6MFpxczEwZTZaMDBZb1ArWks5VGV1YjMKUG95eEhPSGxhcWxUZlI4WUJrVDdDTU84QUk0T1I1VGE0M1hrY3BrMXFaZlZPVzNlK2JVbDBoZHZ4MFB0YUl0bgp3eExMMFBMSmRzTi9BaVpsSXpNUlQzZFRWZHlmdFMxcXkrNHZ0RjlOcXMxU0JKMCs3S1YzN2Fzd1RpTVVPZDdUCjJYTGpVUmI4YmFzTXUrYVJxNjUvcEtsZXRnMDdYbXBPUWtlWmpLMjVvSEdYOURCcHg3NWhVeFBzQ2toczZLTGgKQlFtL05mOGFIMnVSM2I3V2J4R0IzeXFVTjV6UDJwOEQ4T0tjeldVNkpUZXFJUnJVMFpoQURIZlBCT0RyVEVUZQowQ0xNbnpzWThsWEpuaHpOSFY1UkpnK2VpTWJSN3dJREFRQUJBb0lDQUR0OHMwMDdkMTRUQ3NMMHFOc3cxMktECkNORGYvNDJWSGhKZE9ZM2RIZHVxQm1TV1hqTjVWYnM3M2kvbnJBdWlyOXlockxDWEVDNHRmUDZNTENDWjBEUFEKUU1GUmE5YTBtRFJabitFcGxXUkh5NC8vdW1GTitvcVdHdEdHMVEzd3BZeGdtMzA5MTl4RThvWS9OOXhpc1gwTApxYnRrc3cxcTI3L3RaVUhXTXV3M2hrK2pEdTUzMEhvQW8wakZBbU52S2JyM0xweUlVSkwrSGQ3eUR3aW1zMXE2CmExTCtmVkNtVmpuSTJHZmtZTVJ2Y3NNTnlQRlErRkRwV1QrKzlxNm5mMVJJcktvMzM4SSs3bzM4OUVMZ0VxM3YKM3E4ZU9NeGpHbERiTG93ZDBtV3Ivdk1uNG9ld3d3a2ZqWUpZR1VmZzd6S3ZCUm1oVFhqY1VTNUhKSG9HYXc3aAo4WTBEZitIbEIwRWQ2TUlqS3BmMDh5QkdRNTlwRzZoTVNDcmUwSzBTOGxyNENMRHpxbUhDL3Fkb25lekVPMDBnCk1PVGRvZmJSVTJmaFNvV2tMRUw3UXA2ZmNDcFFrazVsVXRqOU1SRDhuaG4zZ1pKRlZoMWk3TDRhNWZKUTNaUWEKejdoUDFVVWNjVnQvWjBYNWM4L3l6L2VEdzZpVkYwUXZOTkQ5NnFQVVZhWWlJSWd2czVKWnIwTEZENjRlMWEzaQp0UjdaS1dyd0x3UUtISXJkZnM0UXRCUEMwUVIvc0NZQXBSTXVVQWJwYmhWUnJpdDBxSFZPeGJRbUZKam9yNTZ5CjQwMTJST29RTXphcERialZJeGdkUDR2ZjlIT2JhaGhMUjVPVy9mbjFLUU9SOC82em9UOThQSG1Wb1JYSW1OV3oKMUdJZGpGSkNSbUpERGI1L0VXdkJBb0lCQVFESWZkUkM2V3djYzdmdW1jcGhCeWpGc0pKc1dPTTFRUkc2WHBVaApjZU1MRXVPMGFkS2N5SDM5VjZDV2dLeTlnLzlZVFF6aC93SGNRaDhJTTV6c3V6d1h2ZldCcXhsRE1Ya2g4QVBrCmJQczNxSlZOZmtIQW9La3BRWHliNGNtNVRKSzM4V0lYTVh4QUtOek82SFZ0dTluOGEzM3ArQ0I3VW5DNTVMWHUKK21sNDRpOGUzcnJlbnJhcmNmV0Ftc2JyK1V6SGJpUHkxTWxvRWJuekZPZ0NXN0RTZEk4MTVPRXBaRnVyT3lBeAozYnM2OUYvN2xZNXF2VERiaHRsMzMzdG1mdTNsUXVHcHVqK0hZSEpCVmo5YTNOamVQRFJtY0VXOVNNcjBrTHlxClZoTlhLZlVlZTZMaWtrdlU1YWRva083K2kwOG9yd1ZKVCtyNzBJNnFOSjlLRkluaEFvSUJBUURwck5udzZ4VDMKcVd1UU0vK012cURTTkFuZzMrSEV6UW1Sd2U0M25hMUtoVXNEN3E1dEpRZDlNY1RFQjkzWUI1Qk13MlRNYnZrcwo1NVAvM0hScUdHait1dnRURXVNZFJxZGZUSnF3bSt1RFNHUDh3MFFzcmVHSjFFVmJqN0F2cVNKbFVkRXEzbHEwClE0UHArWWFrVjVuNzZQY0VLL2JGWmsyTUtFa3VqVXBMYllBMWtxUUkrbWllK2p0cVh5Z2hKbFJzU3AwMzliN0cKZ01naHNUNndtTm56RTZ6ZUtKYis4cnBBWnhwaHMvNVdnZ20xWUpMZmZPTDNQUDVKOGM4SmdEUkZ4SFlxY3FOUgpQQUVWcTlYVTFOd0hCbkltWm9WQ0d4RmhVcVF0U2cvYUg0eEhhTStlZE16QXZKcDF6VTh1VXh2ZVVkM3FuTEoyCjBidmhsSHgzRi9YUEFvSUJBUUNXNjRrOXVTSmxwSUlmZk9zSDhQQ3pKNENCU01QMzBYeDcwempsVFNxQXFuNTMKejNUNENrQTAxYUtQeUZxLzZqM3JoVXllVG14akZlN3dLSklHN2NhRmhMdnJHUlhTSzhxb1RsbFB1TzIrYnd1OApvcjd5TnI0L3pRajArcGowQUgwdE0rb0gxWHRYZktzQ29aL0xzNjJhd251dEJOZkduVDgzMzc5SUhuNEl2aEtFClpWczd1ek44aVRNcDFOakt4d1lSMVlvQUFFZUFMRi9TYUxsaEZRNWN6ZHNEMGIvZ2ZhZzNsREZlK0M4NWFMMWIKejYrK0Q1NVlrZ0RmcUgzbW9NT0pZYmduSUlrejZkajM3K21QQjFIRXNJTXRYWFJSMFJyZDBKb0VpdmpTRTBadAo1Tm12UGpzUkRyTGZGc0toSDlLOXFFb05icmhQU0NZc1g2Sk1qSHRoQW9JQkFGZG96Q0dkbmp0Q1NiVW9ia2gwCnFtM1cvM2I1NkZjWXA2SzVXMlc4ZXEwc2pUSU1YM3orWS8zRjF5ZkpGWWdRMUQ2U3ZLcm9QQmM3RFJhaG5YNXUKWVNBZ1M1RDR1TElqMHNvSU9ya2pxZ1p5MXN3Zk11cFBwTlZNN1NEaDcvTDBIcVN0NVVOVzU3RVVyRXlpaFdZRwpTdGg3ZmFNMjJ0bGVlbFdhWUQya3BvenVpZzRBSzVJY01YUitnQ2s3TFNTeFZOVXBXQVF0emM0dE1DL0NRSVFJCnVVWi9McVB2cFFQN1FnYURTdEFQWjIxdXVUajZ4aFlKZmxFanBLOStYNGJiU1RKcjJoUG96QVRBSUZ1OTZuL3gKbTlpWTVheE5uejFxS0tjeUphMjNUWlMzUmw4VDZzNElQT3MvNGJ0OU1FaFEyRlRpWk5PMUIrRUFkWHBkYysyTQpyZXNDZ2dFQkFKQW1rWW52WmsyMFdsT0tObDZxUHp1djdjZ1pXbFpzLytOdU9qRXBzSGRFTTNrenYyQU5QMEd6CmN4Y21icit2MmZ2NnBKOTJaMGRsdy9lVHc4K3JEQk81dXpXTEFadnNGRll0WUNhODRXNi9Tdy8vMXVmd0wxUzMKV01ubExyaXVyL0tYVE5hNzRyLzF5TE5KdGMzNThkYUlwZXR4ZlJBSGpDdExZVUY2RUNhZDg5RE40L0tZNksyMgpOUDN4UTFPZHRPSGNWNDhZeDdTdkpGV2NSQWgxeDFjdDA4cHJ2V1BnZXhNN0JHTExteGkwRmYyeG9BaU1lWk4rCmcrTUhmb2hYbzNLbEtCY0FJcWRDWXAzcXJja0FIN1V6NVp2TkxhYlpkclBnWWdLbG5Sdnc2N3FpTnJ0WGhyU2YKZFZPSnBTUitFdWxCbzk5OWVMUDQ1aE5pMGFPRW5XRT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo="} \ No newline at end of file