1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "Common.h"
7 
8 #define EXPIRED_TIME_SEC (PR_Now() / PR_USEC_PER_SEC - 3600)
9 #define NOTEXPIRED_TIME_SEC (PR_Now() / PR_USEC_PER_SEC + 3600)
10 
11 #define CACHED_URL "cache.com/"_ns
12 #define NEG_CACHE_EXPIRED_URL "cache.negExpired.com/"_ns
13 #define POS_CACHE_EXPIRED_URL "cache.posExpired.com/"_ns
14 #define BOTH_CACHE_EXPIRED_URL "cache.negAndposExpired.com/"_ns
15 
SetupCacheEntry(LookupCacheV2 * aLookupCache,const nsCString & aCompletion,bool aNegExpired=false,bool aPosExpired=false)16 static void SetupCacheEntry(LookupCacheV2* aLookupCache,
17                             const nsCString& aCompletion,
18                             bool aNegExpired = false,
19                             bool aPosExpired = false) {
20   AddCompleteArray completes;
21   AddCompleteArray emptyCompletes;
22   MissPrefixArray misses;
23   MissPrefixArray emptyMisses;
24 
25   AddComplete* add = completes.AppendElement(fallible);
26   add->complete.FromPlaintext(aCompletion);
27 
28   Prefix* prefix = misses.AppendElement(fallible);
29   prefix->FromPlaintext(aCompletion);
30 
31   // Setup positive cache first otherwise negative cache expiry will be
32   // overwritten.
33   int64_t posExpirySec = aPosExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC;
34   aLookupCache->AddGethashResultToCache(completes, emptyMisses, posExpirySec);
35 
36   int64_t negExpirySec = aNegExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC;
37   aLookupCache->AddGethashResultToCache(emptyCompletes, misses, negExpirySec);
38 }
39 
SetupCacheEntry(LookupCacheV4 * aLookupCache,const nsCString & aCompletion,bool aNegExpired=false,bool aPosExpired=false)40 static void SetupCacheEntry(LookupCacheV4* aLookupCache,
41                             const nsCString& aCompletion,
42                             bool aNegExpired = false,
43                             bool aPosExpired = false) {
44   FullHashResponseMap map;
45 
46   Prefix prefix;
47   prefix.FromPlaintext(aCompletion);
48 
49   CachedFullHashResponse* response = map.GetOrInsertNew(prefix.ToUint32());
50 
51   response->negativeCacheExpirySec =
52       aNegExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC;
53   response->fullHashes.InsertOrUpdate(
54       CreatePrefixFromURL(aCompletion, COMPLETE_SIZE),
55       aPosExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC);
56 
57   aLookupCache->AddFullHashResponseToCache(map);
58 }
59 
60 template <typename T>
TestCache(const Completion aCompletion,bool aExpectedHas,bool aExpectedConfirmed,bool aExpectedInCache,T * aCache=nullptr)61 static void TestCache(const Completion aCompletion, bool aExpectedHas,
62                       bool aExpectedConfirmed, bool aExpectedInCache,
63                       T* aCache = nullptr) {
64   bool has, inCache, confirmed;
65   uint32_t matchLength;
66 
67   if (aCache) {
68     aCache->Has(aCompletion, &has, &matchLength, &confirmed);
69     inCache = aCache->IsInCache(aCompletion.ToUint32());
70   } else {
71     _PrefixArray array = {CreatePrefixFromURL("cache.notexpired.com/", 10),
72                           CreatePrefixFromURL("cache.expired.com/", 8),
73                           CreatePrefixFromURL("gound.com/", 5),
74                           CreatePrefixFromURL("small.com/", 4)};
75 
76     RefPtr<T> cache = SetupLookupCache<T>(array);
77 
78     // Create an expired entry and a non-expired entry
79     SetupCacheEntry(cache, "cache.notexpired.com/"_ns);
80     SetupCacheEntry(cache, "cache.expired.com/"_ns, true, true);
81 
82     cache->Has(aCompletion, &has, &matchLength, &confirmed);
83     inCache = cache->IsInCache(aCompletion.ToUint32());
84   }
85 
86   EXPECT_EQ(has, aExpectedHas);
87   EXPECT_EQ(confirmed, aExpectedConfirmed);
88   EXPECT_EQ(inCache, aExpectedInCache);
89 }
90 
91 template <typename T>
TestCache(const nsCString & aURL,bool aExpectedHas,bool aExpectedConfirmed,bool aExpectedInCache,T * aCache=nullptr)92 static void TestCache(const nsCString& aURL, bool aExpectedHas,
93                       bool aExpectedConfirmed, bool aExpectedInCache,
94                       T* aCache = nullptr) {
95   Completion lookupHash;
96   lookupHash.FromPlaintext(aURL);
97 
98   TestCache<T>(lookupHash, aExpectedHas, aExpectedConfirmed, aExpectedInCache,
99                aCache);
100 }
101 
102 // This testcase check the returned result of |Has| API if fullhash cannot match
103 // any prefix in the local database.
TEST(UrlClassifierCaching,NotFound)104 TEST(UrlClassifierCaching, NotFound)
105 {
106   TestCache<LookupCacheV2>("nomatch.com/"_ns, false, false, false);
107   TestCache<LookupCacheV4>("nomatch.com/"_ns, false, false, false);
108 }
109 
110 // This testcase check the returned result of |Has| API if fullhash find a match
111 // in the local database but not in the cache.
TEST(UrlClassifierCaching,NotInCache)112 TEST(UrlClassifierCaching, NotInCache)
113 {
114   TestCache<LookupCacheV2>("gound.com/"_ns, true, false, false);
115   TestCache<LookupCacheV4>("gound.com/"_ns, true, false, false);
116 }
117 
118 // This testcase check the returned result of |Has| API if fullhash matches
119 // a cache entry in positive cache.
TEST(UrlClassifierCaching,InPositiveCacheNotExpired)120 TEST(UrlClassifierCaching, InPositiveCacheNotExpired)
121 {
122   TestCache<LookupCacheV2>("cache.notexpired.com/"_ns, true, true, true);
123   TestCache<LookupCacheV4>("cache.notexpired.com/"_ns, true, true, true);
124 }
125 
126 // This testcase check the returned result of |Has| API if fullhash matches
127 // a cache entry in positive cache but that it is expired.
TEST(UrlClassifierCaching,InPositiveCacheExpired)128 TEST(UrlClassifierCaching, InPositiveCacheExpired)
129 {
130   TestCache<LookupCacheV2>("cache.expired.com/"_ns, true, false, true);
131   TestCache<LookupCacheV4>("cache.expired.com/"_ns, true, false, true);
132 }
133 
134 // This testcase check the returned result of |Has| API if fullhash matches
135 // a cache entry in negative cache.
TEST(UrlClassifierCaching,InNegativeCacheNotExpired)136 TEST(UrlClassifierCaching, InNegativeCacheNotExpired)
137 {
138   // Create a fullhash whose prefix matches the prefix in negative cache
139   // but completion doesn't match any fullhash in positive cache.
140 
141   Completion prefix;
142   prefix.FromPlaintext("cache.notexpired.com/"_ns);
143 
144   Completion fullhash;
145   fullhash.FromPlaintext("firefox.com/"_ns);
146 
147   // Overwrite the 4-byte prefix of `fullhash` so that it conflicts with
148   // `prefix`. Since "cache.notexpired.com" is added to database in TestCache as
149   // a 10-byte prefix, we should copy more than 10 bytes to fullhash to ensure
150   // it can match the prefix in database.
151   memcpy(fullhash.buf, prefix.buf, 10);
152 
153   TestCache<LookupCacheV2>(fullhash, false, false, true);
154   TestCache<LookupCacheV4>(fullhash, false, false, true);
155 }
156 
157 // This testcase check the returned result of |Has| API if fullhash matches
158 // a cache entry in negative cache but that entry is expired.
TEST(UrlClassifierCaching,InNegativeCacheExpired)159 TEST(UrlClassifierCaching, InNegativeCacheExpired)
160 {
161   // Create a fullhash whose prefix is in the cache.
162 
163   Completion prefix;
164   prefix.FromPlaintext("cache.expired.com/"_ns);
165 
166   Completion fullhash;
167   fullhash.FromPlaintext("firefox.com/"_ns);
168 
169   memcpy(fullhash.buf, prefix.buf, 10);
170 
171   TestCache<LookupCacheV2>(fullhash, true, false, true);
172   TestCache<LookupCacheV4>(fullhash, true, false, true);
173 }
174 
175 // This testcase create 4 cache entries.
176 // 1. unexpired entry.
177 // 2. an entry whose negative cache time is expired but whose positive cache
178 //    is not expired.
179 // 3. an entry whose positive cache time is expired
180 // 4. an entry whose negative cache time and positive cache time are expired
181 // After calling |InvalidateExpiredCacheEntry| API, entries with expired
182 // negative time should be removed from cache(2 & 4)
183 template <typename T>
TestInvalidateExpiredCacheEntry()184 void TestInvalidateExpiredCacheEntry() {
185   _PrefixArray array = {CreatePrefixFromURL(CACHED_URL, 10),
186                         CreatePrefixFromURL(NEG_CACHE_EXPIRED_URL, 8),
187                         CreatePrefixFromURL(POS_CACHE_EXPIRED_URL, 5),
188                         CreatePrefixFromURL(BOTH_CACHE_EXPIRED_URL, 4)};
189   RefPtr<T> cache = SetupLookupCache<T>(array);
190 
191   SetupCacheEntry(cache, CACHED_URL, false, false);
192   SetupCacheEntry(cache, NEG_CACHE_EXPIRED_URL, true, false);
193   SetupCacheEntry(cache, POS_CACHE_EXPIRED_URL, false, true);
194   SetupCacheEntry(cache, BOTH_CACHE_EXPIRED_URL, true, true);
195 
196   // Before invalidate
197   TestCache<T>(CACHED_URL, true, true, true, cache.get());
198   TestCache<T>(NEG_CACHE_EXPIRED_URL, true, true, true, cache.get());
199   TestCache<T>(POS_CACHE_EXPIRED_URL, true, false, true, cache.get());
200   TestCache<T>(BOTH_CACHE_EXPIRED_URL, true, false, true, cache.get());
201 
202   // Call InvalidateExpiredCacheEntry to remove cache entries whose negative
203   // cache time is expired
204   cache->InvalidateExpiredCacheEntries();
205 
206   // After invalidate, NEG_CACHE_EXPIRED_URL & BOTH_CACHE_EXPIRED_URL should
207   // not be found in cache.
208   TestCache<T>(NEG_CACHE_EXPIRED_URL, true, false, false, cache.get());
209   TestCache<T>(BOTH_CACHE_EXPIRED_URL, true, false, false, cache.get());
210 
211   // Other entries should remain the same result.
212   TestCache<T>(CACHED_URL, true, true, true, cache.get());
213   TestCache<T>(POS_CACHE_EXPIRED_URL, true, false, true, cache.get());
214 }
215 
TEST(UrlClassifierCaching,InvalidateExpiredCacheEntryV2)216 TEST(UrlClassifierCaching, InvalidateExpiredCacheEntryV2)
217 { TestInvalidateExpiredCacheEntry<LookupCacheV2>(); }
218 
TEST(UrlClassifierCaching,InvalidateExpiredCacheEntryV4)219 TEST(UrlClassifierCaching, InvalidateExpiredCacheEntryV4)
220 { TestInvalidateExpiredCacheEntry<LookupCacheV4>(); }
221 
222 // This testcase check if an cache entry whose negative cache time is expired
223 // and it doesn't have any postive cache entries in it, it should be removed
224 // from cache after calling |Has|.
TEST(UrlClassifierCaching,NegativeCacheExpireV2)225 TEST(UrlClassifierCaching, NegativeCacheExpireV2)
226 {
227   _PrefixArray array = {CreatePrefixFromURL(NEG_CACHE_EXPIRED_URL, 8)};
228   RefPtr<LookupCacheV2> cache = SetupLookupCache<LookupCacheV2>(array);
229 
230   nsCOMPtr<nsICryptoHash> cryptoHash =
231       do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID);
232 
233   MissPrefixArray misses;
234   Prefix* prefix = misses.AppendElement(fallible);
235   prefix->FromPlaintext(NEG_CACHE_EXPIRED_URL);
236 
237   AddCompleteArray dummy;
238   cache->AddGethashResultToCache(dummy, misses, EXPIRED_TIME_SEC);
239 
240   // Ensure it is in cache in the first place.
241   EXPECT_EQ(cache->IsInCache(prefix->ToUint32()), true);
242 
243   // It should be removed after calling Has API.
244   TestCache<LookupCacheV2>(NEG_CACHE_EXPIRED_URL, true, false, false,
245                            cache.get());
246 }
247 
TEST(UrlClassifierCaching,NegativeCacheExpireV4)248 TEST(UrlClassifierCaching, NegativeCacheExpireV4)
249 {
250   _PrefixArray array = {CreatePrefixFromURL(NEG_CACHE_EXPIRED_URL, 8)};
251   RefPtr<LookupCacheV4> cache = SetupLookupCache<LookupCacheV4>(array);
252 
253   FullHashResponseMap map;
254   Prefix prefix;
255   nsCOMPtr<nsICryptoHash> cryptoHash =
256       do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID);
257   prefix.FromPlaintext(NEG_CACHE_EXPIRED_URL);
258   CachedFullHashResponse* response = map.GetOrInsertNew(prefix.ToUint32());
259 
260   response->negativeCacheExpirySec = EXPIRED_TIME_SEC;
261 
262   cache->AddFullHashResponseToCache(map);
263 
264   // Ensure it is in cache in the first place.
265   EXPECT_EQ(cache->IsInCache(prefix.ToUint32()), true);
266 
267   // It should be removed after calling Has API.
268   TestCache<LookupCacheV4>(NEG_CACHE_EXPIRED_URL, true, false, false,
269                            cache.get());
270 }
271