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