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