1 // Copyright 2015 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 // Note: Read the class comment of AndroidAffiliationService for the definition
6 // of the terms used below.
7 //
8 // On-demand fetching strategy
9 //
10 // A GetAffiliationsAndBranding() request concerning facet X will be served from
11 // the cache as long as the cache contains fresh affiliation information for
12 // facet X, that is, if there is an equivalence class in the cache that contains
13 // X and has been fetched less than |kCacheHardExpiryInHours| hours ago.
14 //
15 // Otherwise, a network request is issued against the Affiliation API as soon as
16 // possible, that is, immediately if there is no fetch in flight, or right after
17 // completion of the fetch in flight if there is one, provided that the required
18 // data is not incidentally returned by the first fetch.
19 //
20 //
21 // Proactive fetching strategy
22 //
23 // A Prefetch() request concerning facet Y can trigger an initial network fetch,
24 // or periodic refetches only when:
25 // * The prefetch request is not already expired, i.e., its |keep_fresh_until|
26 // threshold is strictly in the future (that is, prefetch intervals are open
27 // from the right).
28 // * Affiliation information in the cache pertaining to facet Y will get stale
29 // strictly before the specified |keep_fresh_until| threshold.
30 //
31 // An initial fetch will be issued as soon as possible if, in addition to the
32 // two necessery conditions above, and at the time of the Prefetch() call, the
33 // cache contains no affiliation information regarding facet Y, or if the data
34 // in the cache for facet Y is near-stale, that is, it has been fetched more
35 // than |kCacheHardExpiryInHours| hours ago.
36 //
37 // A refetch will be issued every time the data in the cache regarding facet Y
38 // becomes near-stale, that is, exactly |kCacheSoftExpiry| hours after the last
39 // fetch, provided that the above two necessary conditions are also met.
40 //
41 // Fetches are triggered already when the data gets near-stale, as opposed to
42 // waiting until the data would get stale, in an effort to keep the data fresh
43 // even in face of temporary network errors lasting no more than the difference
44 // between soft and hard expiry times.
45 //
46 // The current fetch scheduling logic, however, can only deal with at most one
47 // such 'early' fetch between taking place between the prior fetch and the
48 // corresponding hard expiry time of the data, therefore it is assumed that:
49 //
50 // kCacheSoftExpiryInHours < kCacheHardExpiryInHours, and
51 // 2 * kCacheSoftExpiryInHours > kCacheHardExpiryInHours.
52 //
53 //
54 // Cache freshness terminology
55 //
56 //
57 // Fetch (t=0) kCacheSoftExpiry kCacheHardExpiry
58 // / / /
59 // ---o------------------------o-----------------------o-----------------> t
60 // | | |
61 // | [-- Cache near-stale --------------------- ..
62 // [--------------- Cache is fresh ----------------)[-- Cache is stale ..
63 //
64
65 #include "components/password_manager/core/browser/android_affiliation/facet_manager.h"
66
67 #include "base/bind.h"
68 #include "base/location.h"
69 #include "base/task_runner.h"
70 #include "base/time/clock.h"
71 #include "base/time/time.h"
72 #include "components/password_manager/core/browser/android_affiliation/facet_manager_host.h"
73
74 namespace password_manager {
75
76 // statics
77 const int FacetManager::kCacheSoftExpiryInHours = 21;
78 const int FacetManager::kCacheHardExpiryInHours = 24;
79
80 static_assert(
81 FacetManager::kCacheSoftExpiryInHours <
82 FacetManager::kCacheHardExpiryInHours,
83 "Soft expiry period must be shorter than the hard expiry period.");
84
85 static_assert(
86 2 * FacetManager::kCacheSoftExpiryInHours >
87 FacetManager::kCacheHardExpiryInHours,
88 "Soft expiry period must be longer than half of the hard expiry period.");
89
90 // Encapsulates the details of a pending GetAffiliationsAndBranding() request.
91 struct FacetManager::RequestInfo {
92 AndroidAffiliationService::ResultCallback callback;
93 scoped_refptr<base::TaskRunner> callback_task_runner;
94 };
95
FacetManager(const FacetURI & facet_uri,FacetManagerHost * backend,base::Clock * clock)96 FacetManager::FacetManager(const FacetURI& facet_uri,
97 FacetManagerHost* backend,
98 base::Clock* clock)
99 : facet_uri_(facet_uri), backend_(backend), clock_(clock) {
100 AffiliatedFacetsWithUpdateTime affiliations;
101 if (backend_->ReadAffiliationsAndBrandingFromDatabase(facet_uri_,
102 &affiliations))
103 last_update_time_ = affiliations.last_update_time;
104 }
105
~FacetManager()106 FacetManager::~FacetManager() {
107 // The manager will be destroyed while there are pending requests only if the
108 // entire backend is going away. Fail pending requests in this case.
109 for (auto& request_info : pending_requests_)
110 ServeRequestWithFailure(std::move(request_info));
111 }
112
GetAffiliationsAndBranding(StrategyOnCacheMiss cache_miss_strategy,AndroidAffiliationService::ResultCallback callback,const scoped_refptr<base::TaskRunner> & callback_task_runner)113 void FacetManager::GetAffiliationsAndBranding(
114 StrategyOnCacheMiss cache_miss_strategy,
115 AndroidAffiliationService::ResultCallback callback,
116 const scoped_refptr<base::TaskRunner>& callback_task_runner) {
117 RequestInfo request_info;
118 request_info.callback = std::move(callback);
119 request_info.callback_task_runner = callback_task_runner;
120 if (IsCachedDataFresh()) {
121 AffiliatedFacetsWithUpdateTime affiliation;
122 if (!backend_->ReadAffiliationsAndBrandingFromDatabase(facet_uri_,
123 &affiliation)) {
124 ServeRequestWithFailure(std::move(request_info));
125 return;
126 }
127 DCHECK_EQ(affiliation.last_update_time, last_update_time_) << facet_uri_;
128 ServeRequestWithSuccess(std::move(request_info), affiliation.facets);
129 } else if (cache_miss_strategy == StrategyOnCacheMiss::FETCH_OVER_NETWORK) {
130 pending_requests_.push_back(std::move(request_info));
131 backend_->SignalNeedNetworkRequest();
132 } else {
133 ServeRequestWithFailure(std::move(request_info));
134 }
135 }
136
Prefetch(const base::Time & keep_fresh_until)137 void FacetManager::Prefetch(const base::Time& keep_fresh_until) {
138 keep_fresh_until_thresholds_.insert(keep_fresh_until);
139
140 // If an initial fetch if needed, trigger that (the refetch will be scheduled
141 // once the initial fetch completes). Otherwise schedule the next refetch.
142 base::Time next_required_fetch(GetNextRequiredFetchTimeDueToPrefetch());
143 if (next_required_fetch <= clock_->Now())
144 backend_->SignalNeedNetworkRequest();
145 else if (next_required_fetch < base::Time::Max())
146 backend_->RequestNotificationAtTime(facet_uri_, next_required_fetch);
147
148 // For a finite |keep_fresh_until|, schedule a callback so that once the
149 // prefetch expires, it can be removed from |keep_fresh_untils_|, and also the
150 // manager can get a chance to be destroyed unless it is otherwise needed.
151 if (keep_fresh_until > clock_->Now() && keep_fresh_until < base::Time::Max())
152 backend_->RequestNotificationAtTime(facet_uri_, keep_fresh_until);
153 }
154
CancelPrefetch(const base::Time & keep_fresh_until)155 void FacetManager::CancelPrefetch(const base::Time& keep_fresh_until) {
156 auto iter = keep_fresh_until_thresholds_.find(keep_fresh_until);
157 if (iter != keep_fresh_until_thresholds_.end())
158 keep_fresh_until_thresholds_.erase(iter);
159 }
160
OnFetchSucceeded(const AffiliatedFacetsWithUpdateTime & affiliation)161 void FacetManager::OnFetchSucceeded(
162 const AffiliatedFacetsWithUpdateTime& affiliation) {
163 last_update_time_ = affiliation.last_update_time;
164 DCHECK(IsCachedDataFresh()) << facet_uri_;
165 for (auto& request_info : pending_requests_)
166 ServeRequestWithSuccess(std::move(request_info), affiliation.facets);
167 pending_requests_.clear();
168
169 base::Time next_required_fetch(GetNextRequiredFetchTimeDueToPrefetch());
170 if (next_required_fetch < base::Time::Max())
171 backend_->RequestNotificationAtTime(facet_uri_, next_required_fetch);
172 }
173
NotifyAtRequestedTime()174 void FacetManager::NotifyAtRequestedTime() {
175 base::Time next_required_fetch(GetNextRequiredFetchTimeDueToPrefetch());
176 if (next_required_fetch <= clock_->Now())
177 backend_->SignalNeedNetworkRequest();
178 else if (next_required_fetch < base::Time::Max())
179 backend_->RequestNotificationAtTime(facet_uri_, next_required_fetch);
180
181 auto iter_first_non_expired =
182 keep_fresh_until_thresholds_.upper_bound(clock_->Now());
183 keep_fresh_until_thresholds_.erase(keep_fresh_until_thresholds_.begin(),
184 iter_first_non_expired);
185 }
186
CanBeDiscarded() const187 bool FacetManager::CanBeDiscarded() const {
188 return pending_requests_.empty() &&
189 GetMaximumKeepFreshUntilThreshold() <= clock_->Now();
190 }
191
CanCachedDataBeDiscarded() const192 bool FacetManager::CanCachedDataBeDiscarded() const {
193 return GetMaximumKeepFreshUntilThreshold() <= clock_->Now() ||
194 !IsCachedDataFresh();
195 }
196
DoesRequireFetch() const197 bool FacetManager::DoesRequireFetch() const {
198 return (!pending_requests_.empty() && !IsCachedDataFresh()) ||
199 GetNextRequiredFetchTimeDueToPrefetch() <= clock_->Now();
200 }
201
IsCachedDataFresh() const202 bool FacetManager::IsCachedDataFresh() const {
203 return clock_->Now() < GetCacheHardExpiryTime();
204 }
205
IsCachedDataNearStale() const206 bool FacetManager::IsCachedDataNearStale() const {
207 return GetCacheSoftExpiryTime() <= clock_->Now();
208 }
209
GetCacheSoftExpiryTime() const210 base::Time FacetManager::GetCacheSoftExpiryTime() const {
211 return last_update_time_ +
212 base::TimeDelta::FromHours(kCacheSoftExpiryInHours);
213 }
214
GetCacheHardExpiryTime() const215 base::Time FacetManager::GetCacheHardExpiryTime() const {
216 return last_update_time_ +
217 base::TimeDelta::FromHours(kCacheHardExpiryInHours);
218 }
219
GetMaximumKeepFreshUntilThreshold() const220 base::Time FacetManager::GetMaximumKeepFreshUntilThreshold() const {
221 return !keep_fresh_until_thresholds_.empty()
222 ? *keep_fresh_until_thresholds_.rbegin()
223 : base::Time();
224 }
225
GetNextRequiredFetchTimeDueToPrefetch() const226 base::Time FacetManager::GetNextRequiredFetchTimeDueToPrefetch() const {
227 // If there is at least one non-expired Prefetch() request that requires the
228 // data to be kept fresh until some time later than its current hard expiry
229 // time, then a fetch is needed once the cached data becomes near-stale.
230 if (clock_->Now() < GetMaximumKeepFreshUntilThreshold() &&
231 GetCacheHardExpiryTime() < GetMaximumKeepFreshUntilThreshold()) {
232 return GetCacheSoftExpiryTime();
233 }
234 return base::Time::Max();
235 }
236
237 // static
ServeRequestWithSuccess(RequestInfo request_info,const AffiliatedFacets & affiliation)238 void FacetManager::ServeRequestWithSuccess(
239 RequestInfo request_info,
240 const AffiliatedFacets& affiliation) {
241 request_info.callback_task_runner->PostTask(
242 FROM_HERE,
243 base::BindOnce(std::move(request_info.callback), affiliation, true));
244 }
245
246 // static
ServeRequestWithFailure(RequestInfo request_info)247 void FacetManager::ServeRequestWithFailure(RequestInfo request_info) {
248 request_info.callback_task_runner->PostTask(
249 FROM_HERE, base::BindOnce(std::move(request_info.callback),
250 AffiliatedFacets(), false));
251 }
252
253 } // namespace password_manager
254