fune/security/manager/ssl/DataStorage.h

205 lines
7.8 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_DataStorage_h
#define mozilla_DataStorage_h
#include "mozilla/Atomics.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/StaticPtr.h"
#include "nsCOMPtr.h"
#include "nsTHashMap.h"
#include "nsIDataStorage.h"
#include "nsIMemoryReporter.h"
#include "nsIObserver.h"
#include "nsITimer.h"
#include "nsRefPtrHashtable.h"
#include "nsString.h"
namespace mozilla {
class TaskQueue;
/**
* DataStorage is a threadsafe, generic, narrow string-based hash map that
* persists data on disk and additionally handles temporary and private data.
* However, if used in a context where there is no profile directory, data
* will not be persisted.
*
* Its lifecycle is as follows:
* - Allocate with a filename (this is or will eventually be a file in the
* profile directory, if the profile exists).
* - Call Init() from the main thread. This spins off an asynchronous read
* of the backing file.
* - Eventually observers of the topic "data-storage-ready" will be notified
* with the backing filename as the data in the notification when this
* has completed.
* - Should the profile directory not be available, (e.g. in xpcshell),
* DataStorage will not initially read any persistent data. The
* "data-storage-ready" event will still be emitted. This follows semantics
* similar to the permission manager and allows tests that test unrelated
* components to proceed without a profile.
* - A timer periodically fires on a background thread that checks if any
* persistent data has changed, and if so writes all persistent data to the
* backing file. When this happens, observers will be notified with the
* topic "data-storage-written" and the backing filename as the data.
* It is possible to receive a "data-storage-written" event while there exist
* pending persistent data changes. However, those changes will eventually be
* written when the timer fires again, and eventually another
* "data-storage-written" event will be sent.
* - When a DataStorage instance observes the topic "profile-before-change" in
* anticipation of shutdown, all persistent data for that DataStorage is
* written to the backing file (this blocks the main thread). In the process
* of doing this, the background serial event target responsible for these
* writes is then shut down to prevent further writes to that file (the
* background timer is also cancelled when this happens).
* If "profile-before-change" is not observed, this happens upon observing
* "xpcom-shutdown-threads".
* - For testing purposes, the preference "test.datastorage.write_timer_ms" can
* be set to cause the asynchronous writing of data to happen more quickly.
* - To prevent unbounded memory and disk use, the number of entries in each
* table is limited to 1024. Evictions are handled in by a modified LRU scheme
* (see implementation comments).
* - NB: Instances of DataStorage have long lifetimes because they are strong
* observers of events and won't go away until the observer service does.
*
* For each key/value:
* - The key must be a non-empty string containing no instances of '\t' or '\n'
* (this is a limitation of how the data is stored and will be addressed in
* the future).
* - The key must have a length no more than 256.
* - The value must not contain '\n' and must have a length no more than 1024.
* (the length limits are to prevent unbounded disk and memory usage)
*/
class DataStorageManager final : public nsIDataStorageManager {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDATASTORAGEMANAGER
private:
~DataStorageManager() = default;
bool mAlternateServicesCreated = false;
bool mClientAuthRememberListCreated = false;
bool mSiteSecurityServiceStateCreated = false;
};
class DataStorageItem final : public nsIDataStorageItem {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDATASTORAGEITEM
DataStorageItem(const nsACString& aKey, const nsACString& aValue,
nsIDataStorage::DataType aType)
: key(aKey), value(aValue), type(aType) {}
private:
~DataStorageItem() = default;
nsAutoCString key;
nsAutoCString value;
nsIDataStorage::DataType type;
};
class DataStorage final : public nsIDataStorage,
public nsIMemoryReporter,
public nsIObserver {
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDATASTORAGE
NS_DECL_NSIMEMORYREPORTER
NS_DECL_NSIOBSERVER
explicit DataStorage(const nsString& aFilename);
// Initializes the DataStorage. Must be called before using.
nsresult Init();
private:
~DataStorage() = default;
void ArmTimer(const MutexAutoLock& aProofOfLock);
void ShutdownTimer();
class Writer;
class Reader;
class Entry {
public:
Entry();
bool UpdateScore();
uint32_t mScore;
int32_t mLastAccessed; // the last accessed time in days since the epoch
nsCString mValue;
};
// Utility class for scanning tables for an entry to evict.
class KeyAndEntry {
public:
nsCString mKey;
Entry mEntry;
};
typedef nsTHashMap<nsCStringHashKey, Entry> DataStorageTable;
typedef nsRefPtrHashtable<nsStringHashKey, DataStorage> DataStorages;
void WaitForReady();
nsresult AsyncWriteData(const MutexAutoLock& aProofOfLock);
nsresult AsyncReadData(const MutexAutoLock& aProofOfLock);
static nsresult ValidateKeyAndValue(const nsACString& aKey,
const nsACString& aValue);
static void TimerCallback(nsITimer* aTimer, void* aClosure);
void NotifyObservers(const char* aTopic);
bool GetInternal(const nsACString& aKey, Entry* aEntry,
nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock);
nsresult PutInternal(const nsACString& aKey, Entry& aEntry,
nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock);
void MaybeEvictOneEntry(nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock);
DataStorageTable& GetTableForType(nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock);
void ReadAllFromTable(nsIDataStorage::DataType aType,
nsTArray<RefPtr<nsIDataStorageItem>>& aItems,
const MutexAutoLock& aProofOfLock);
Mutex mMutex; // This mutex protects access to the following members:
DataStorageTable mPersistentDataTable MOZ_GUARDED_BY(mMutex);
DataStorageTable mTemporaryDataTable MOZ_GUARDED_BY(mMutex);
DataStorageTable mPrivateDataTable MOZ_GUARDED_BY(mMutex);
nsCOMPtr<nsIFile> mBackingFile MOZ_GUARDED_BY(mMutex);
bool mPendingWrite MOZ_GUARDED_BY(
mMutex); // true if a write is needed but hasn't been dispatched
bool mTimerArmed MOZ_GUARDED_BY(mMutex);
bool mShuttingDown MOZ_GUARDED_BY(mMutex);
RefPtr<TaskQueue> mBackgroundTaskQueue MOZ_GUARDED_BY(mMutex);
// (End list of members protected by mMutex)
nsCOMPtr<nsITimer> mTimer;
mozilla::Atomic<bool> mInitCalled; // Indicates that Init() has been called.
uint32_t mTimerDelayMS;
Monitor mReadyMonitor; // Do not acquire this at the same time as mMutex.
bool mReady MOZ_GUARDED_BY(mReadyMonitor); // Indicates that saved data has
// been read and Get can proceed.
const nsString mFilename;
static StaticAutoPtr<DataStorages> sDataStorages;
};
} // namespace mozilla
#endif // mozilla_DataStorage_h