1 /*
2 ** Copyright 2011, The Android Open Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 ** http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18
19 #include "BlobCache.h"
20
21 #include <errno.h>
22 #include <inttypes.h>
23
24 #if defined(__ANDROID__)
25 #include <cutils/properties.h>
26 #else
27 #include <string.h>
28
29 #include <algorithm>
30 static const char property_value[] = "[HOST]";
31 #define PROPERTY_VALUE_MAX (sizeof(property_value) - 1)
property_get(const char * key,char * value,const char * default_value)32 static int property_get(const char* key, char* value, const char* default_value) {
33 if (!strcmp(key, "ro.build.id")) {
34 memcpy(value, property_value, PROPERTY_VALUE_MAX);
35 return PROPERTY_VALUE_MAX;
36 }
37 if (default_value) {
38 const size_t len = std::max(strlen(default_value) + 1, size_t(PROPERTY_VALUE_MAX));
39 memcpy(value, default_value, len);
40 }
41 return 0;
42 }
43 #endif
44
45 #include <log/log.h>
46
47 #include <algorithm>
48 #include <chrono>
49 #include <memory>
50
51 namespace android {
52
53 // BlobCache::Header::mMagicNumber value
54 static const uint32_t blobCacheMagic = ('_' << 24) + ('B' << 16) + ('b' << 8) + '$';
55
56 // BlobCache::Header::mBlobCacheVersion value
57 static const uint32_t blobCacheVersion = 3;
58
59 // BlobCache::Header::mDeviceVersion value
60 static const uint32_t blobCacheDeviceVersion = 1;
61
BlobCache(size_t maxKeySize,size_t maxValueSize,size_t maxTotalSize,Policy policy)62 BlobCache::BlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize, Policy policy)
63 : mMaxKeySize(maxKeySize),
64 mMaxValueSize(maxValueSize),
65 mMaxTotalSize(maxTotalSize),
66 mPolicySelect(policy.first),
67 mPolicyCapacity(policy.second),
68 mTotalSize(0),
69 mAccessCount(0) {
70 int64_t now = std::chrono::steady_clock::now().time_since_epoch().count();
71 #ifdef _WIN32
72 srand(now);
73 #else
74 mRandState[0] = (now >> 0) & 0xFFFF;
75 mRandState[1] = (now >> 16) & 0xFFFF;
76 mRandState[2] = (now >> 32) & 0xFFFF;
77 #endif
78 ALOGV("initializing random seed using %lld", (unsigned long long)now);
79 }
80
set(const void * key,size_t keySize,const void * value,size_t valueSize)81 void BlobCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) {
82 if (mMaxKeySize < keySize) {
83 ALOGV("set: not caching because the key is too large: %zu (limit: %zu)", keySize,
84 mMaxKeySize);
85 return;
86 }
87 if (mMaxValueSize < valueSize) {
88 ALOGV("set: not caching because the value is too large: %zu (limit: %zu)", valueSize,
89 mMaxValueSize);
90 return;
91 }
92 if (mMaxTotalSize < keySize + valueSize) {
93 ALOGV("set: not caching because the combined key/value size is too "
94 "large: %zu (limit: %zu)",
95 keySize + valueSize, mMaxTotalSize);
96 return;
97 }
98 if (keySize == 0) {
99 ALOGW("set: not caching because keySize is 0");
100 return;
101 }
102 if (valueSize <= 0) {
103 ALOGW("set: not caching because valueSize is 0");
104 return;
105 }
106
107 std::shared_ptr<Blob> dummyKey(new Blob(key, keySize, false));
108 CacheEntry dummyEntry(dummyKey, NULL, 0);
109
110 while (true) {
111 auto index = std::lower_bound(mCacheEntries.begin(), mCacheEntries.end(), dummyEntry);
112 if (index == mCacheEntries.end() || dummyEntry < *index) {
113 // Create a new cache entry.
114 std::shared_ptr<Blob> keyBlob(new Blob(key, keySize, true));
115 std::shared_ptr<Blob> valueBlob(new Blob(value, valueSize, true));
116 size_t newEntrySize = keySize + valueSize;
117 size_t newTotalSize = mTotalSize + newEntrySize;
118 if (mMaxTotalSize < newTotalSize) {
119 if (isCleanable()) {
120 // Clean the cache and try again.
121 if (!clean(newEntrySize, NoEntry)) {
122 // We have some kind of logic error -- perhaps
123 // an inconsistency between isCleanable() and
124 // findDownTo().
125 ALOGE("set: not caching new key/value pair because "
126 "cleaning failed");
127 break;
128 }
129 continue;
130 } else {
131 ALOGV("set: not caching new key/value pair because the "
132 "total cache size limit would be exceeded: %zu "
133 "(limit: %zu)",
134 keySize + valueSize, mMaxTotalSize);
135 break;
136 }
137 }
138 mCacheEntries.insert(index, CacheEntry(keyBlob, valueBlob, ++mAccessCount));
139 mTotalSize = newTotalSize;
140 ALOGV("set: created new cache entry with %zu byte key and %zu byte value", keySize,
141 valueSize);
142 } else {
143 // Update the existing cache entry.
144 std::shared_ptr<Blob> valueBlob(new Blob(value, valueSize, true));
145 std::shared_ptr<Blob> oldValueBlob(index->getValue());
146 size_t newTotalSize = mTotalSize + valueSize - oldValueBlob->getSize();
147 if (mMaxTotalSize < newTotalSize) {
148 if (isCleanable()) {
149 // Clean the cache and try again.
150 if (!clean(index->getKey()->getSize() + valueSize,
151 index - mCacheEntries.begin())) {
152 // We have some kind of logic error -- perhaps
153 // an inconsistency between isCleanable() and
154 // findDownTo().
155 ALOGE("set: not caching new value because "
156 "cleaning failed");
157 break;
158 }
159 continue;
160 } else {
161 ALOGV("set: not caching new value because the total cache "
162 "size limit would be exceeded: %zu (limit: %zu)",
163 keySize + valueSize, mMaxTotalSize);
164 break;
165 }
166 }
167 index->setValue(valueBlob);
168 index->setRecency(++mAccessCount);
169 mTotalSize = newTotalSize;
170 ALOGV("set: updated existing cache entry with %zu byte key and %zu byte "
171 "value",
172 keySize, valueSize);
173 }
174 break;
175 }
176 }
177
get(const void * key,size_t keySize,void * value,size_t valueSize)178 size_t BlobCache::get(const void* key, size_t keySize, void* value, size_t valueSize) {
179 void* dummy;
180 return get(key, keySize, &dummy, [value, valueSize](size_t allocSize) {
181 return (allocSize <= valueSize ? value : nullptr);
182 });
183 }
184
get(const void * key,size_t keySize,void ** value,std::function<void * (size_t)> alloc)185 size_t BlobCache::get(const void* key, size_t keySize, void** value,
186 std::function<void*(size_t)> alloc) {
187 if (mMaxKeySize < keySize) {
188 ALOGV("get: not searching because the key is too large: %zu (limit %zu)", keySize,
189 mMaxKeySize);
190 *value = nullptr;
191 return 0;
192 }
193 std::shared_ptr<Blob> dummyKey(new Blob(key, keySize, false));
194 CacheEntry dummyEntry(dummyKey, NULL, 0);
195 auto index = std::lower_bound(mCacheEntries.begin(), mCacheEntries.end(), dummyEntry);
196 if (index == mCacheEntries.end() || dummyEntry < *index) {
197 ALOGV("get: no cache entry found for key of size %zu", keySize);
198 *value = nullptr;
199 return 0;
200 }
201
202 // The key was found. Return the value if we can allocate a buffer.
203 std::shared_ptr<Blob> valueBlob(index->getValue());
204 size_t valueBlobSize = valueBlob->getSize();
205 void* buf = alloc(valueBlobSize);
206 if (buf != nullptr) {
207 ALOGV("get: copying %zu bytes to caller's buffer", valueBlobSize);
208 memcpy(buf, valueBlob->getData(), valueBlobSize);
209 *value = buf;
210 index->setRecency(++mAccessCount);
211 } else {
212 ALOGV("get: cannot allocate caller's buffer: needs %zu", valueBlobSize);
213 *value = nullptr;
214 }
215 return valueBlobSize;
216 }
217
align_sizet(size_t size)218 static inline size_t align_sizet(size_t size) {
219 constexpr size_t alignment = alignof(size_t) - 1;
220 return (size + alignment) & ~alignment;
221 }
222
getFlattenedSize() const223 size_t BlobCache::getFlattenedSize() const {
224 size_t size = align_sizet(sizeof(Header) + PROPERTY_VALUE_MAX);
225 for (const CacheEntry& e : mCacheEntries) {
226 std::shared_ptr<Blob> const& keyBlob = e.getKey();
227 std::shared_ptr<Blob> const& valueBlob = e.getValue();
228 size += align_sizet(sizeof(EntryHeader) + keyBlob->getSize() + valueBlob->getSize());
229 }
230 return size;
231 }
232
flatten(void * buffer,size_t size) const233 int BlobCache::flatten(void* buffer, size_t size) const {
234 // Write the cache header
235 if (size < sizeof(Header)) {
236 ALOGE("flatten: not enough room for cache header");
237 return 0;
238 }
239 Header* header = reinterpret_cast<Header*>(buffer);
240 header->mMagicNumber = blobCacheMagic;
241 header->mBlobCacheVersion = blobCacheVersion;
242 header->mDeviceVersion = blobCacheDeviceVersion;
243 header->mNumEntries = mCacheEntries.size();
244 char buildId[PROPERTY_VALUE_MAX];
245 header->mBuildIdLength = property_get("ro.build.id", buildId, "");
246 memcpy(header->mBuildId, buildId, header->mBuildIdLength);
247
248 // Write cache entries
249 uint8_t* byteBuffer = reinterpret_cast<uint8_t*>(buffer);
250 off_t byteOffset = align_sizet(sizeof(Header) + header->mBuildIdLength);
251 for (const CacheEntry& e : mCacheEntries) {
252 std::shared_ptr<Blob> const& keyBlob = e.getKey();
253 std::shared_ptr<Blob> const& valueBlob = e.getValue();
254 size_t keySize = keyBlob->getSize();
255 size_t valueSize = valueBlob->getSize();
256
257 size_t entrySize = sizeof(EntryHeader) + keySize + valueSize;
258 size_t totalSize = align_sizet(entrySize);
259 if (byteOffset + totalSize > size) {
260 ALOGE("flatten: not enough room for cache entries");
261 return -EINVAL;
262 }
263
264 EntryHeader* eheader = reinterpret_cast<EntryHeader*>(&byteBuffer[byteOffset]);
265 eheader->mKeySize = keySize;
266 eheader->mValueSize = valueSize;
267
268 memcpy(eheader->mData, keyBlob->getData(), keySize);
269 memcpy(eheader->mData + keySize, valueBlob->getData(), valueSize);
270
271 if (totalSize > entrySize) {
272 // We have padding bytes. Those will get written to storage, and contribute to the CRC,
273 // so make sure we zero-them to have reproducible results.
274 memset(eheader->mData + keySize + valueSize, 0, totalSize - entrySize);
275 }
276
277 byteOffset += totalSize;
278 }
279
280 return 0;
281 }
282
unflatten(void const * buffer,size_t size)283 int BlobCache::unflatten(void const* buffer, size_t size) {
284 // All errors should result in the BlobCache being in an empty state.
285 mCacheEntries.clear();
286
287 // Read the cache header
288 if (size < sizeof(Header)) {
289 ALOGE("unflatten: not enough room for cache header");
290 return -EINVAL;
291 }
292 const Header* header = reinterpret_cast<const Header*>(buffer);
293 if (header->mMagicNumber != blobCacheMagic) {
294 ALOGE("unflatten: bad magic number: %" PRIu32, header->mMagicNumber);
295 return -EINVAL;
296 }
297 char buildId[PROPERTY_VALUE_MAX];
298 int len = property_get("ro.build.id", buildId, "");
299 if (header->mBlobCacheVersion != blobCacheVersion ||
300 header->mDeviceVersion != blobCacheDeviceVersion || len != header->mBuildIdLength ||
301 strncmp(buildId, header->mBuildId, len)) {
302 // We treat version mismatches as an empty cache.
303 return 0;
304 }
305
306 // Read cache entries
307 const uint8_t* byteBuffer = reinterpret_cast<const uint8_t*>(buffer);
308 off_t byteOffset = align_sizet(sizeof(Header) + header->mBuildIdLength);
309 size_t numEntries = header->mNumEntries;
310 for (size_t i = 0; i < numEntries; i++) {
311 if (byteOffset + sizeof(EntryHeader) > size) {
312 mCacheEntries.clear();
313 ALOGE("unflatten: not enough room for cache entry header");
314 return -EINVAL;
315 }
316
317 const EntryHeader* eheader = reinterpret_cast<const EntryHeader*>(&byteBuffer[byteOffset]);
318 size_t keySize = eheader->mKeySize;
319 size_t valueSize = eheader->mValueSize;
320 size_t entrySize = sizeof(EntryHeader) + keySize + valueSize;
321
322 size_t totalSize = align_sizet(entrySize);
323 if (byteOffset + totalSize > size) {
324 mCacheEntries.clear();
325 ALOGE("unflatten: not enough room for cache entry");
326 return -EINVAL;
327 }
328
329 const uint8_t* data = eheader->mData;
330 set(data, keySize, data + keySize, valueSize);
331
332 byteOffset += totalSize;
333 }
334
335 return 0;
336 }
337
blob_random()338 long int BlobCache::blob_random() {
339 #ifdef _WIN32
340 return rand();
341 #else
342 return nrand48(mRandState);
343 #endif
344 }
345
findVictim()346 size_t BlobCache::findVictim() {
347 switch (mPolicySelect) {
348 case Select::RANDOM:
349 return size_t(blob_random() % (mCacheEntries.size()));
350 case Select::LRU:
351 return std::min_element(mCacheEntries.begin(), mCacheEntries.end(),
352 [](const CacheEntry& a, const CacheEntry& b) {
353 return a.getRecency() < b.getRecency();
354 }) -
355 mCacheEntries.begin();
356 default:
357 ALOGE("findVictim: unknown mPolicySelect: %d", mPolicySelect);
358 return 0;
359 }
360 }
361
findDownTo(size_t newEntrySize,size_t onBehalfOf)362 size_t BlobCache::findDownTo(size_t newEntrySize, size_t onBehalfOf) {
363 auto oldEntrySize = [this, onBehalfOf]() -> size_t {
364 if (onBehalfOf == NoEntry) return 0;
365 const auto& entry = mCacheEntries[onBehalfOf];
366 return entry.getKey()->getSize() + entry.getValue()->getSize();
367 };
368 switch (mPolicyCapacity) {
369 case Capacity::HALVE:
370 return mMaxTotalSize / 2;
371 case Capacity::FIT:
372 return mMaxTotalSize - (newEntrySize - oldEntrySize());
373 case Capacity::FIT_HALVE:
374 return std::min(mMaxTotalSize - (newEntrySize - oldEntrySize()), mMaxTotalSize / 2);
375 default:
376 ALOGE("findDownTo: unknown mPolicyCapacity: %d", mPolicyCapacity);
377 return 0;
378 }
379 }
380
isFit(Capacity capacity)381 bool BlobCache::isFit(Capacity capacity) {
382 switch (capacity) {
383 case Capacity::HALVE:
384 return false;
385 case Capacity::FIT:
386 case Capacity::FIT_HALVE:
387 return true;
388 default:
389 ALOGE("isFit: unknown capacity: %d", capacity);
390 return false;
391 }
392 }
393
clean(size_t newEntrySize,size_t onBehalfOf)394 bool BlobCache::clean(size_t newEntrySize, size_t onBehalfOf) {
395 // Remove a selected cache entry until the total cache size does
396 // not exceed downTo.
397 const size_t downTo = findDownTo(newEntrySize, onBehalfOf);
398
399 bool cleaned = false;
400 while (mTotalSize > downTo) {
401 const size_t i = findVictim();
402 const CacheEntry& entry(mCacheEntries[i]);
403 const size_t entrySize = entry.getKey()->getSize() + entry.getValue()->getSize();
404 mTotalSize -= entrySize;
405 mCacheEntries.erase(mCacheEntries.begin() + i);
406 cleaned = true;
407 }
408 return cleaned;
409 }
410
isCleanable() const411 bool BlobCache::isCleanable() const {
412 switch (mPolicyCapacity) {
413 case Capacity::HALVE:
414 return mTotalSize > mMaxTotalSize / 2;
415 default:
416 ALOGE("isCleanable: unknown mPolicyCapacity: %d", mPolicyCapacity);
417 [[fallthrough]];
418 case Capacity::FIT:
419 case Capacity::FIT_HALVE:
420 return mTotalSize > 0;
421 }
422 }
423
Blob(const void * data,size_t size,bool copyData)424 BlobCache::Blob::Blob(const void* data, size_t size, bool copyData)
425 : mData(copyData ? malloc(size) : data), mSize(size), mOwnsData(copyData) {
426 if (data != NULL && copyData) {
427 memcpy(const_cast<void*>(mData), data, size);
428 }
429 }
430
~Blob()431 BlobCache::Blob::~Blob() {
432 if (mOwnsData) {
433 free(const_cast<void*>(mData));
434 }
435 }
436
operator <(const Blob & rhs) const437 bool BlobCache::Blob::operator<(const Blob& rhs) const {
438 if (mSize == rhs.mSize) {
439 return memcmp(mData, rhs.mData, mSize) < 0;
440 } else {
441 return mSize < rhs.mSize;
442 }
443 }
444
getData() const445 const void* BlobCache::Blob::getData() const {
446 return mData;
447 }
448
getSize() const449 size_t BlobCache::Blob::getSize() const {
450 return mSize;
451 }
452
CacheEntry()453 BlobCache::CacheEntry::CacheEntry() : mRecency(0) {}
454
CacheEntry(const std::shared_ptr<Blob> & key,const std::shared_ptr<Blob> & value,uint32_t recency)455 BlobCache::CacheEntry::CacheEntry(const std::shared_ptr<Blob>& key,
456 const std::shared_ptr<Blob>& value, uint32_t recency)
457 : mKey(key), mValue(value), mRecency(recency) {}
458
CacheEntry(const CacheEntry & ce)459 BlobCache::CacheEntry::CacheEntry(const CacheEntry& ce)
460 : mKey(ce.mKey), mValue(ce.mValue), mRecency(ce.mRecency) {}
461
operator <(const CacheEntry & rhs) const462 bool BlobCache::CacheEntry::operator<(const CacheEntry& rhs) const {
463 return *mKey < *rhs.mKey;
464 }
465
operator =(const CacheEntry & rhs)466 const BlobCache::CacheEntry& BlobCache::CacheEntry::operator=(const CacheEntry& rhs) {
467 mKey = rhs.mKey;
468 mValue = rhs.mValue;
469 mRecency = rhs.mRecency;
470 return *this;
471 }
472
getKey() const473 std::shared_ptr<BlobCache::Blob> BlobCache::CacheEntry::getKey() const {
474 return mKey;
475 }
476
getValue() const477 std::shared_ptr<BlobCache::Blob> BlobCache::CacheEntry::getValue() const {
478 return mValue;
479 }
480
setValue(const std::shared_ptr<Blob> & value)481 void BlobCache::CacheEntry::setValue(const std::shared_ptr<Blob>& value) {
482 mValue = value;
483 }
484
getRecency() const485 uint32_t BlobCache::CacheEntry::getRecency() const {
486 return mRecency;
487 }
488
setRecency(uint32_t recency)489 void BlobCache::CacheEntry::setRecency(uint32_t recency) {
490 mRecency = recency;
491 }
492
493 } // namespace android
494