1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chromeos/components/tether/top_level_host_scan_cache.h"
6 
7 #include <algorithm>
8 
9 #include "base/bind.h"
10 #include "base/memory/ptr_util.h"
11 #include "chromeos/components/multidevice/logging/logging.h"
12 #include "chromeos/components/tether/active_host.h"
13 #include "chromeos/components/tether/persistent_host_scan_cache.h"
14 #include "chromeos/components/tether/timer_factory.h"
15 
16 namespace chromeos {
17 
18 namespace tether {
19 
TopLevelHostScanCache(std::unique_ptr<TimerFactory> timer_factory,ActiveHost * active_host,HostScanCache * network_host_scan_cache,PersistentHostScanCache * persistent_host_scan_cache)20 TopLevelHostScanCache::TopLevelHostScanCache(
21     std::unique_ptr<TimerFactory> timer_factory,
22     ActiveHost* active_host,
23     HostScanCache* network_host_scan_cache,
24     PersistentHostScanCache* persistent_host_scan_cache)
25     : timer_factory_(std::move(timer_factory)),
26       active_host_(active_host),
27       network_host_scan_cache_(network_host_scan_cache),
28       persistent_host_scan_cache_(persistent_host_scan_cache),
29       is_initializing_(false) {
30   InitializeFromPersistentCache();
31 }
32 
~TopLevelHostScanCache()33 TopLevelHostScanCache::~TopLevelHostScanCache() {
34   DCHECK(ActiveHost::ActiveHostStatus::DISCONNECTED ==
35          active_host_->GetActiveHostStatus());
36   for (const auto& tether_guid : GetTetherGuidsInCache())
37     RemoveHostScanResult(tether_guid);
38 }
39 
SetHostScanResult(const HostScanCacheEntry & entry)40 void TopLevelHostScanCache::SetHostScanResult(const HostScanCacheEntry& entry) {
41   auto found_iter = tether_guid_to_timer_map_.find(entry.tether_network_guid);
42   if (found_iter == tether_guid_to_timer_map_.end()) {
43     // Only check whether this entry exists in the cache after intialization
44     // completes; otherwise, this will cause an error when the persistent cache
45     // has entries that the other caches do not have.
46     DCHECK(is_initializing_ || !ExistsInCache(entry.tether_network_guid));
47 
48     // If no Timer exists in the map, add one.
49     tether_guid_to_timer_map_.emplace(entry.tether_network_guid,
50                                       timer_factory_->CreateOneShotTimer());
51   } else {
52     DCHECK(ExistsInCache(entry.tether_network_guid));
53 
54     // If a timer was already running for this entry, stop it. It is started
55     // again in the StartTimer() call below since the entry now has fresh data.
56     found_iter->second->Stop();
57   }
58 
59   // Set the result in the sub-caches.
60   network_host_scan_cache_->SetHostScanResult(entry);
61   persistent_host_scan_cache_->SetHostScanResult(entry);
62 
63   StartTimer(entry.tether_network_guid);
64 }
65 
RemoveHostScanResultImpl(const std::string & tether_network_guid)66 bool TopLevelHostScanCache::RemoveHostScanResultImpl(
67     const std::string& tether_network_guid) {
68   DCHECK(!tether_network_guid.empty());
69 
70   if (active_host_->GetTetherNetworkGuid() == tether_network_guid) {
71     DCHECK(ExistsInCache(tether_network_guid));
72     PA_LOG(VERBOSE) << "RemoveHostScanResult() called for Tether network with "
73                     << "GUID " << tether_network_guid << ", but the "
74                     << "corresponding device is the active host. Not removing "
75                     << "this scan result from the cache.";
76     return false;
77   }
78 
79   if (!ExistsInCache(tether_network_guid)) {
80     PA_LOG(ERROR) << "Attempted to remove a host scan result which does not "
81                   << "exist in the cache. GUID: " << tether_network_guid;
82     return false;
83   }
84 
85   bool removed_from_network =
86       network_host_scan_cache_->RemoveHostScanResult(tether_network_guid);
87   bool removed_from_persistent =
88       persistent_host_scan_cache_->RemoveHostScanResult(tether_network_guid);
89   bool removed_from_timer_map =
90       tether_guid_to_timer_map_.erase(tether_network_guid) == 1u;
91 
92   // The caches are expected to remain in sync, so it should not be possible
93   // for one of them to be removed successfully while the other one fails.
94   DCHECK(removed_from_network && removed_from_persistent &&
95          removed_from_timer_map);
96 
97   PA_LOG(VERBOSE) << "Removed cache entry with GUID \"" << tether_network_guid
98                   << "\".";
99 
100   // We already DCHECK()ed above that this evaluates to true, but we return the
101   // AND'ed value here because without this, release builds (without DCHECK())
102   // will produce a compiler warning of unused variables.
103   return removed_from_network && removed_from_persistent &&
104          removed_from_timer_map;
105 }
106 
GetTetherGuidsInCache()107 std::unordered_set<std::string> TopLevelHostScanCache::GetTetherGuidsInCache() {
108   std::unordered_set<std::string> tether_guids;
109   for (const auto& entry : tether_guid_to_timer_map_)
110     tether_guids.insert(entry.first);
111   DCHECK(tether_guids == persistent_host_scan_cache_->GetTetherGuidsInCache() &&
112          tether_guids == network_host_scan_cache_->GetTetherGuidsInCache());
113   return tether_guids;
114 }
115 
ExistsInCache(const std::string & tether_network_guid)116 bool TopLevelHostScanCache::ExistsInCache(
117     const std::string& tether_network_guid) {
118   bool exists_in_network_cache =
119       network_host_scan_cache_->ExistsInCache(tether_network_guid);
120   bool exists_in_persistent_cache =
121       persistent_host_scan_cache_->ExistsInCache(tether_network_guid);
122   bool exists_in_timer_map =
123       tether_guid_to_timer_map_.find(tether_network_guid) !=
124       tether_guid_to_timer_map_.end();
125 
126   // The caches are expected to remain in sync.
127   DCHECK(exists_in_network_cache == exists_in_persistent_cache &&
128          exists_in_persistent_cache == exists_in_timer_map);
129 
130   // We already DCHECK()ed above that these are equal, but we return the AND'ed
131   // value here because without this, release builds (without DCHECK())
132   // will produce a compiler warning of unused variables.
133   return exists_in_network_cache && exists_in_persistent_cache &&
134          exists_in_timer_map;
135 }
136 
DoesHostRequireSetup(const std::string & tether_network_guid)137 bool TopLevelHostScanCache::DoesHostRequireSetup(
138     const std::string& tether_network_guid) {
139   // |network_host_scan_cache_| does not keep track of this value since the
140   // networking stack does not store it internally. Instead, query
141   // |persistent_host_scan_cache_|.
142   return persistent_host_scan_cache_->DoesHostRequireSetup(tether_network_guid);
143 }
144 
InitializeFromPersistentCache()145 void TopLevelHostScanCache::InitializeFromPersistentCache() {
146   is_initializing_ = true;
147 
148   // If a crash occurs, Tether networks which were previously present will no
149   // longer be available since they are only stored within NetworkStateHandler
150   // and not within Shill. Thus, utilize |persistent_host_scan_cache_| to fetch
151   // metadata about all Tether networks which were present before the crash and
152   // restore |network_host_scan_cache_|.
153   std::unordered_map<std::string, HostScanCacheEntry> persisted_entries =
154       persistent_host_scan_cache_->GetStoredCacheEntries();
155   for (const auto& it : persisted_entries) {
156     SetHostScanResult(it.second);
157   }
158 
159   is_initializing_ = false;
160 }
161 
StartTimer(const std::string & tether_network_guid)162 void TopLevelHostScanCache::StartTimer(const std::string& tether_network_guid) {
163   auto found_iter = tether_guid_to_timer_map_.find(tether_network_guid);
164   DCHECK(found_iter != tether_guid_to_timer_map_.end());
165   DCHECK(!found_iter->second->IsRunning());
166 
167   PA_LOG(VERBOSE)
168       << "Starting host scan cache timer for Tether network with GUID "
169       << "\"" << tether_network_guid << "\". Will fire in "
170       << kNumMinutesBeforeCacheEntryExpires << " minutes.";
171 
172   found_iter->second->Start(
173       FROM_HERE,
174       base::TimeDelta::FromMinutes(kNumMinutesBeforeCacheEntryExpires),
175       base::BindOnce(&TopLevelHostScanCache::OnTimerFired,
176                      weak_ptr_factory_.GetWeakPtr(), tether_network_guid));
177 }
178 
OnTimerFired(const std::string & tether_network_guid)179 void TopLevelHostScanCache::OnTimerFired(
180     const std::string& tether_network_guid) {
181   if (active_host_->GetTetherNetworkGuid() == tether_network_guid) {
182     // Log as a warning. This situation should be uncommon in practice since
183     // KeepAliveScheduler should schedule a new keep-alive status update every
184     // 4 minutes.
185     PA_LOG(WARNING) << "Timer fired for Tether network GUID \""
186                     << tether_network_guid << "\", but the corresponding "
187                     << "device is the active host. Restarting timer.";
188 
189     // If the Timer which fired corresponds to the active host, do not remove
190     // the cache entry. The active host must always remain in the cache so that
191     // the UI can reflect that it is the connecting/connected network. In this
192     // case, just restart the timer.
193     StartTimer(tether_network_guid);
194     return;
195   }
196 
197   PA_LOG(VERBOSE) << "Timer fired for Tether network GUID "
198                   << tether_network_guid << ". Removing stale scan result.";
199   RemoveHostScanResult(tether_network_guid);
200 }
201 
202 }  // namespace tether
203 
204 }  // namespace chromeos
205