| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/components/tether/master_host_scan_cache.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "chromeos/components/multidevice/logging/logging.h" |
| #include "chromeos/components/tether/active_host.h" |
| #include "chromeos/components/tether/persistent_host_scan_cache.h" |
| #include "chromeos/components/tether/timer_factory.h" |
| |
| namespace chromeos { |
| |
| namespace tether { |
| |
| MasterHostScanCache::MasterHostScanCache( |
| std::unique_ptr<TimerFactory> timer_factory, |
| ActiveHost* active_host, |
| HostScanCache* network_host_scan_cache, |
| PersistentHostScanCache* persistent_host_scan_cache) |
| : timer_factory_(std::move(timer_factory)), |
| active_host_(active_host), |
| network_host_scan_cache_(network_host_scan_cache), |
| persistent_host_scan_cache_(persistent_host_scan_cache), |
| is_initializing_(false), |
| weak_ptr_factory_(this) { |
| InitializeFromPersistentCache(); |
| } |
| |
| MasterHostScanCache::~MasterHostScanCache() { |
| DCHECK(ActiveHost::ActiveHostStatus::DISCONNECTED == |
| active_host_->GetActiveHostStatus()); |
| for (const auto& tether_guid : GetTetherGuidsInCache()) |
| RemoveHostScanResult(tether_guid); |
| } |
| |
| void MasterHostScanCache::SetHostScanResult(const HostScanCacheEntry& entry) { |
| auto found_iter = tether_guid_to_timer_map_.find(entry.tether_network_guid); |
| if (found_iter == tether_guid_to_timer_map_.end()) { |
| // Only check whether this entry exists in the cache after intialization |
| // completes; otherwise, this will cause an error when the persistent cache |
| // has entries that the other caches do not have. |
| DCHECK(is_initializing_ || !ExistsInCache(entry.tether_network_guid)); |
| |
| // If no Timer exists in the map, add one. |
| tether_guid_to_timer_map_.emplace(entry.tether_network_guid, |
| timer_factory_->CreateOneShotTimer()); |
| } else { |
| DCHECK(ExistsInCache(entry.tether_network_guid)); |
| |
| // If a timer was already running for this entry, stop it. It is started |
| // again in the StartTimer() call below since the entry now has fresh data. |
| found_iter->second->Stop(); |
| } |
| |
| // Set the result in the sub-caches. |
| network_host_scan_cache_->SetHostScanResult(entry); |
| persistent_host_scan_cache_->SetHostScanResult(entry); |
| |
| StartTimer(entry.tether_network_guid); |
| } |
| |
| bool MasterHostScanCache::RemoveHostScanResultImpl( |
| const std::string& tether_network_guid) { |
| DCHECK(!tether_network_guid.empty()); |
| |
| if (active_host_->GetTetherNetworkGuid() == tether_network_guid) { |
| DCHECK(ExistsInCache(tether_network_guid)); |
| PA_LOG(VERBOSE) << "RemoveHostScanResult() called for Tether network with " |
| << "GUID " << tether_network_guid << ", but the " |
| << "corresponding device is the active host. Not removing " |
| << "this scan result from the cache."; |
| return false; |
| } |
| |
| if (!ExistsInCache(tether_network_guid)) { |
| PA_LOG(ERROR) << "Attempted to remove a host scan result which does not " |
| << "exist in the cache. GUID: " << tether_network_guid; |
| return false; |
| } |
| |
| bool removed_from_network = |
| network_host_scan_cache_->RemoveHostScanResult(tether_network_guid); |
| bool removed_from_persistent = |
| persistent_host_scan_cache_->RemoveHostScanResult(tether_network_guid); |
| bool removed_from_timer_map = |
| tether_guid_to_timer_map_.erase(tether_network_guid) == 1u; |
| |
| // The caches are expected to remain in sync, so it should not be possible |
| // for one of them to be removed successfully while the other one fails. |
| DCHECK(removed_from_network && removed_from_persistent && |
| removed_from_timer_map); |
| |
| PA_LOG(VERBOSE) << "Removed cache entry with GUID \"" << tether_network_guid |
| << "\"."; |
| |
| // We already DCHECK()ed above that this evaluates to true, but we return the |
| // AND'ed value here because without this, release builds (without DCHECK()) |
| // will produce a compiler warning of unused variables. |
| return removed_from_network && removed_from_persistent && |
| removed_from_timer_map; |
| } |
| |
| std::unordered_set<std::string> MasterHostScanCache::GetTetherGuidsInCache() { |
| std::unordered_set<std::string> tether_guids; |
| for (const auto& entry : tether_guid_to_timer_map_) |
| tether_guids.insert(entry.first); |
| DCHECK(tether_guids == persistent_host_scan_cache_->GetTetherGuidsInCache() && |
| tether_guids == network_host_scan_cache_->GetTetherGuidsInCache()); |
| return tether_guids; |
| } |
| |
| bool MasterHostScanCache::ExistsInCache( |
| const std::string& tether_network_guid) { |
| bool exists_in_network_cache = |
| network_host_scan_cache_->ExistsInCache(tether_network_guid); |
| bool exists_in_persistent_cache = |
| persistent_host_scan_cache_->ExistsInCache(tether_network_guid); |
| bool exists_in_timer_map = |
| tether_guid_to_timer_map_.find(tether_network_guid) != |
| tether_guid_to_timer_map_.end(); |
| |
| // The caches are expected to remain in sync. |
| DCHECK(exists_in_network_cache == exists_in_persistent_cache && |
| exists_in_persistent_cache == exists_in_timer_map); |
| |
| // We already DCHECK()ed above that these are equal, but we return the AND'ed |
| // value here because without this, release builds (without DCHECK()) |
| // will produce a compiler warning of unused variables. |
| return exists_in_network_cache && exists_in_persistent_cache && |
| exists_in_timer_map; |
| } |
| |
| bool MasterHostScanCache::DoesHostRequireSetup( |
| const std::string& tether_network_guid) { |
| // |network_host_scan_cache_| does not keep track of this value since the |
| // networking stack does not store it internally. Instead, query |
| // |persistent_host_scan_cache_|. |
| return persistent_host_scan_cache_->DoesHostRequireSetup(tether_network_guid); |
| } |
| |
| void MasterHostScanCache::InitializeFromPersistentCache() { |
| is_initializing_ = true; |
| |
| // If a crash occurs, Tether networks which were previously present will no |
| // longer be available since they are only stored within NetworkStateHandler |
| // and not within Shill. Thus, utilize |persistent_host_scan_cache_| to fetch |
| // metadata about all Tether networks which were present before the crash and |
| // restore |network_host_scan_cache_|. |
| std::unordered_map<std::string, HostScanCacheEntry> persisted_entries = |
| persistent_host_scan_cache_->GetStoredCacheEntries(); |
| for (const auto& it : persisted_entries) { |
| SetHostScanResult(it.second); |
| } |
| |
| is_initializing_ = false; |
| } |
| |
| void MasterHostScanCache::StartTimer(const std::string& tether_network_guid) { |
| auto found_iter = tether_guid_to_timer_map_.find(tether_network_guid); |
| DCHECK(found_iter != tether_guid_to_timer_map_.end()); |
| DCHECK(!found_iter->second->IsRunning()); |
| |
| PA_LOG(VERBOSE) |
| << "Starting host scan cache timer for Tether network with GUID " |
| << "\"" << tether_network_guid << "\". Will fire in " |
| << kNumMinutesBeforeCacheEntryExpires << " minutes."; |
| |
| found_iter->second->Start( |
| FROM_HERE, |
| base::TimeDelta::FromMinutes(kNumMinutesBeforeCacheEntryExpires), |
| base::Bind(&MasterHostScanCache::OnTimerFired, |
| weak_ptr_factory_.GetWeakPtr(), tether_network_guid)); |
| } |
| |
| void MasterHostScanCache::OnTimerFired(const std::string& tether_network_guid) { |
| if (active_host_->GetTetherNetworkGuid() == tether_network_guid) { |
| // Log as a warning. This situation should be uncommon in practice since |
| // KeepAliveScheduler should schedule a new keep-alive status update every |
| // 4 minutes. |
| PA_LOG(WARNING) << "Timer fired for Tether network GUID \"" |
| << tether_network_guid << "\", but the corresponding " |
| << "device is the active host. Restarting timer."; |
| |
| // If the Timer which fired corresponds to the active host, do not remove |
| // the cache entry. The active host must always remain in the cache so that |
| // the UI can reflect that it is the connecting/connected network. In this |
| // case, just restart the timer. |
| StartTimer(tether_network_guid); |
| return; |
| } |
| |
| PA_LOG(VERBOSE) << "Timer fired for Tether network GUID " |
| << tether_network_guid << ". Removing stale scan result."; |
| RemoveHostScanResult(tether_network_guid); |
| } |
| |
| } // namespace tether |
| |
| } // namespace chromeos |