text: // 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/top_level_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 {
  
  TopLevelHostScanCache::TopLevelHostScanCache(
      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) {
    InitializeFromPersistentCache();
  }
  
  TopLevelHostScanCache::~TopLevelHostScanCache() {
    DCHECK(ActiveHost::ActiveHostStatus::DISCONNECTED ==
           active_host_->GetActiveHostStatus());
    for (const auto& tether_guid : GetTetherGuidsInCache())
      RemoveHostScanResult(tether_guid);
  }
  
  void TopLevelHostScanCache::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 TopLevelHostScanCache::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> TopLevelHostScanCache::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 TopLevelHostScanCache::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 TopLevelHostScanCache::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 TopLevelHostScanCache::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 TopLevelHostScanCache::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::Minutes(kNumMinutesBeforeCacheEntryExpires),
        base::BindOnce(&TopLevelHostScanCache::OnTimerFired,
                       weak_ptr_factory_.GetWeakPtr(), tether_network_guid));
  }
  
  void TopLevelHostScanCache::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
  