summaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authoralexis.menard@intel.com <alexis.menard@intel.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-18 16:26:11 +0000
committeralexis.menard@intel.com <alexis.menard@intel.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-18 16:26:11 +0000
commit30a9071ad365fc3141a5cd30a01559e508e9a891 (patch)
tree78d2517bb5dffaadf5f57e2be1eea33d51fc248d /components
parent715d972e1cd8f4606bb6fe20db4326aca15f248c (diff)
downloadchromium_src-30a9071ad365fc3141a5cd30a01559e508e9a891.zip
chromium_src-30a9071ad365fc3141a5cd30a01559e508e9a891.tar.gz
chromium_src-30a9071ad365fc3141a5cd30a01559e508e9a891.tar.bz2
Move chrome/browser/nacl_host/pnacl_translation_cache.* to components/nacl/browser
These files have no dependencies on chrome/ so they can be safely moved to the components/ directory. This is part of an effort to componentize NaCl code. BUG=244791 Review URL: https://codereview.chromium.org/61763026 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@235735 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
-rw-r--r--components/components_tests.gyp2
-rw-r--r--components/nacl.gyp2
-rw-r--r--components/nacl/browser/DEPS2
-rw-r--r--components/nacl/browser/pnacl_translation_cache.cc440
-rw-r--r--components/nacl/browser/pnacl_translation_cache.h106
-rw-r--r--components/nacl/browser/pnacl_translation_cache_unittest.cc269
-rw-r--r--components/nacl_common.gyp2
7 files changed, 823 insertions, 0 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index 1ec3ed5..990c653 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -135,9 +135,11 @@
['disable_nacl==0', {
'sources': [
'nacl/browser/nacl_validation_cache_unittest.cc',
+ 'nacl/browser/pnacl_translation_cache_unittest.cc',
],
'dependencies': [
'nacl.gyp:nacl_browser',
+ 'nacl_common.gyp:nacl_common',
],
}],
['OS == "android"', {
diff --git a/components/nacl.gyp b/components/nacl.gyp
index 5ec4fce..4e1f804 100644
--- a/components/nacl.gyp
+++ b/components/nacl.gyp
@@ -107,6 +107,8 @@
'nacl/browser/nacl_browser.h',
'nacl/browser/nacl_validation_cache.cc',
'nacl/browser/nacl_validation_cache.h',
+ 'nacl/browser/pnacl_translation_cache.cc',
+ 'nacl/browser/pnacl_translation_cache.h',
],
'include_dirs': [
'..',
diff --git a/components/nacl/browser/DEPS b/components/nacl/browser/DEPS
index 1c35d9c..67b5b47 100644
--- a/components/nacl/browser/DEPS
+++ b/components/nacl/browser/DEPS
@@ -1,3 +1,5 @@
include_rules = [
"+content/public/browser",
+ "+content/public/test",
+ "+net",
]
diff --git a/components/nacl/browser/pnacl_translation_cache.cc b/components/nacl/browser/pnacl_translation_cache.cc
new file mode 100644
index 0000000..e27103a
--- /dev/null
+++ b/components/nacl/browser/pnacl_translation_cache.cc
@@ -0,0 +1,440 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/nacl/browser/pnacl_translation_cache.h"
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_checker.h"
+#include "components/nacl/common/pnacl_types.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+
+using base::IntToString;
+using content::BrowserThread;
+
+namespace {
+
+void CloseDiskCacheEntry(disk_cache::Entry* entry) { entry->Close(); }
+
+} // namespace
+
+namespace pnacl {
+// This is in pnacl namespace instead of static so they can be used
+// by the unit test.
+const int kMaxMemCacheSize = 100 * 1024 * 1024;
+
+//////////////////////////////////////////////////////////////////////
+// Handle Reading/Writing to Cache.
+
+// PnaclTranslationCacheEntry is a shim that provides storage for the
+// 'key' and 'data' strings as the disk_cache is performing various async
+// operations. It also tracks the open disk_cache::Entry
+// and ensures that the entry is closed.
+class PnaclTranslationCacheEntry
+ : public base::RefCounted<PnaclTranslationCacheEntry> {
+ public:
+ static PnaclTranslationCacheEntry* GetReadEntry(
+ base::WeakPtr<PnaclTranslationCache> cache,
+ const std::string& key,
+ const GetNexeCallback& callback);
+ static PnaclTranslationCacheEntry* GetWriteEntry(
+ base::WeakPtr<PnaclTranslationCache> cache,
+ const std::string& key,
+ net::DrainableIOBuffer* write_nexe,
+ const CompletionCallback& callback);
+
+ void Start();
+
+ // Writes: ---
+ // v |
+ // Start -> Open Existing --------------> Write ---> Close
+ // \ ^
+ // \ /
+ // --> Create --
+ // Reads:
+ // Start -> Open --------Read ----> Close
+ // | ^
+ // |__|
+ enum CacheStep {
+ UNINITIALIZED,
+ OPEN_ENTRY,
+ CREATE_ENTRY,
+ TRANSFER_ENTRY,
+ CLOSE_ENTRY,
+ FINISHED
+ };
+
+ private:
+ friend class base::RefCounted<PnaclTranslationCacheEntry>;
+ PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache,
+ const std::string& key,
+ bool is_read);
+ ~PnaclTranslationCacheEntry();
+
+ // Try to open an existing entry in the backend
+ void OpenEntry();
+ // Create a new entry in the backend (for writes)
+ void CreateEntry();
+ // Write |len| bytes to the backend, starting at |offset|
+ void WriteEntry(int offset, int len);
+ // Read |len| bytes from the backend, starting at |offset|
+ void ReadEntry(int offset, int len);
+ // If there was an error, doom the entry. Then post a task to the IO
+ // thread to close (and delete) it.
+ void CloseEntry(int rv);
+ // Call the user callback, and signal to the cache to delete this.
+ void Finish(int rv);
+ // Used as the callback for all operations to the backend. Handle state
+ // transitions, track bytes transferred, and call the other helper methods.
+ void DispatchNext(int rv);
+
+ base::WeakPtr<PnaclTranslationCache> cache_;
+ std::string key_;
+ disk_cache::Entry* entry_;
+ CacheStep step_;
+ bool is_read_;
+ GetNexeCallback read_callback_;
+ CompletionCallback write_callback_;
+ scoped_refptr<net::DrainableIOBuffer> io_buf_;
+ base::ThreadChecker thread_checker_;
+ DISALLOW_COPY_AND_ASSIGN(PnaclTranslationCacheEntry);
+};
+
+// static
+PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetReadEntry(
+ base::WeakPtr<PnaclTranslationCache> cache,
+ const std::string& key,
+ const GetNexeCallback& callback) {
+ PnaclTranslationCacheEntry* entry(
+ new PnaclTranslationCacheEntry(cache, key, true));
+ entry->read_callback_ = callback;
+ return entry;
+}
+
+// static
+PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetWriteEntry(
+ base::WeakPtr<PnaclTranslationCache> cache,
+ const std::string& key,
+ net::DrainableIOBuffer* write_nexe,
+ const CompletionCallback& callback) {
+ PnaclTranslationCacheEntry* entry(
+ new PnaclTranslationCacheEntry(cache, key, false));
+ entry->io_buf_ = write_nexe;
+ entry->write_callback_ = callback;
+ return entry;
+}
+
+PnaclTranslationCacheEntry::PnaclTranslationCacheEntry(
+ base::WeakPtr<PnaclTranslationCache> cache,
+ const std::string& key,
+ bool is_read)
+ : cache_(cache),
+ key_(key),
+ entry_(NULL),
+ step_(UNINITIALIZED),
+ is_read_(is_read) {}
+
+PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() {
+ // Ensure we have called the user's callback
+ if (step_ != FINISHED) {
+ if (!read_callback_.is_null()) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(read_callback_,
+ net::ERR_ABORTED,
+ scoped_refptr<net::DrainableIOBuffer>()));
+ }
+ if (!write_callback_.is_null()) {
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(write_callback_, net::ERR_ABORTED));
+ }
+ }
+}
+
+void PnaclTranslationCacheEntry::Start() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ step_ = OPEN_ENTRY;
+ OpenEntry();
+}
+
+// OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called
+// from DispatchNext, so they know that cache_ is still valid.
+void PnaclTranslationCacheEntry::OpenEntry() {
+ int rv = cache_->backend()->OpenEntry(
+ key_,
+ &entry_,
+ base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this));
+ if (rv != net::ERR_IO_PENDING)
+ DispatchNext(rv);
+}
+
+void PnaclTranslationCacheEntry::CreateEntry() {
+ int rv = cache_->backend()->CreateEntry(
+ key_,
+ &entry_,
+ base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this));
+ if (rv != net::ERR_IO_PENDING)
+ DispatchNext(rv);
+}
+
+void PnaclTranslationCacheEntry::WriteEntry(int offset, int len) {
+ DCHECK(io_buf_->BytesRemaining() == len);
+ int rv = entry_->WriteData(
+ 1,
+ offset,
+ io_buf_.get(),
+ len,
+ base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this),
+ false);
+ if (rv != net::ERR_IO_PENDING)
+ DispatchNext(rv);
+}
+
+void PnaclTranslationCacheEntry::ReadEntry(int offset, int len) {
+ int rv = entry_->ReadData(
+ 1,
+ offset,
+ io_buf_.get(),
+ len,
+ base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this));
+ if (rv != net::ERR_IO_PENDING)
+ DispatchNext(rv);
+}
+
+void PnaclTranslationCacheEntry::CloseEntry(int rv) {
+ DCHECK(entry_);
+ if (rv < 0) {
+ LOG(ERROR) << "Failed to close entry: " << net::ErrorToString(rv);
+ entry_->Doom();
+ }
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE, base::Bind(&CloseDiskCacheEntry, entry_));
+ Finish(rv);
+}
+
+void PnaclTranslationCacheEntry::Finish(int rv) {
+ step_ = FINISHED;
+ if (is_read_) {
+ if (!read_callback_.is_null()) {
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(read_callback_, rv, io_buf_));
+ }
+ } else {
+ if (!write_callback_.is_null()) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE, base::Bind(write_callback_, rv));
+ }
+ }
+ cache_->OpComplete(this);
+}
+
+void PnaclTranslationCacheEntry::DispatchNext(int rv) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!cache_)
+ return;
+
+ switch (step_) {
+ case UNINITIALIZED:
+ case FINISHED:
+ LOG(ERROR) << "DispatchNext called uninitialized";
+ break;
+
+ case OPEN_ENTRY:
+ if (rv == net::OK) {
+ step_ = TRANSFER_ENTRY;
+ if (is_read_) {
+ int bytes_to_transfer = entry_->GetDataSize(1);
+ io_buf_ = new net::DrainableIOBuffer(
+ new net::IOBuffer(bytes_to_transfer), bytes_to_transfer);
+ ReadEntry(0, bytes_to_transfer);
+ } else {
+ WriteEntry(0, io_buf_->size());
+ }
+ } else {
+ if (rv != net::ERR_FAILED) {
+ // ERROR_FAILED is what we expect if the entry doesn't exist.
+ LOG(ERROR) << "OpenEntry failed: " << net::ErrorToString(rv);
+ }
+ if (is_read_) {
+ // Just a cache miss, not necessarily an error.
+ entry_ = NULL;
+ Finish(rv);
+ } else {
+ step_ = CREATE_ENTRY;
+ CreateEntry();
+ }
+ }
+ break;
+
+ case CREATE_ENTRY:
+ if (rv == net::OK) {
+ step_ = TRANSFER_ENTRY;
+ WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
+ } else {
+ LOG(ERROR) << "Failed to Create Entry: " << net::ErrorToString(rv);
+ Finish(rv);
+ }
+ break;
+
+ case TRANSFER_ENTRY:
+ if (rv < 0) {
+ // We do not call DispatchNext directly if WriteEntry/ReadEntry returns
+ // ERR_IO_PENDING, and the callback should not return that value either.
+ LOG(ERROR) << "Failed to complete write to entry: "
+ << net::ErrorToString(rv);
+ step_ = CLOSE_ENTRY;
+ CloseEntry(rv);
+ break;
+ } else if (rv > 0) {
+ io_buf_->DidConsume(rv);
+ if (io_buf_->BytesRemaining() > 0) {
+ is_read_
+ ? ReadEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining())
+ : WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
+ break;
+ }
+ }
+ // rv == 0 or we fell through (i.e. we have transferred all the bytes)
+ step_ = CLOSE_ENTRY;
+ DCHECK(io_buf_->BytesConsumed() == io_buf_->size());
+ if (is_read_)
+ io_buf_->SetOffset(0);
+ CloseEntry(0);
+ break;
+
+ case CLOSE_ENTRY:
+ step_ = UNINITIALIZED;
+ break;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) {
+ open_entries_.erase(entry);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Construction and cache backend initialization
+PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {}
+
+PnaclTranslationCache::~PnaclTranslationCache() {}
+
+int PnaclTranslationCache::Init(net::CacheType cache_type,
+ const base::FilePath& cache_dir,
+ int cache_size,
+ const CompletionCallback& callback) {
+ int rv = disk_cache::CreateCacheBackend(
+ cache_type,
+ net::CACHE_BACKEND_DEFAULT,
+ cache_dir,
+ cache_size,
+ true /* force_initialize */,
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(),
+ NULL, /* dummy net log */
+ &disk_cache_,
+ base::Bind(&PnaclTranslationCache::OnCreateBackendComplete, AsWeakPtr()));
+ if (rv == net::ERR_IO_PENDING) {
+ init_callback_ = callback;
+ }
+ return rv;
+}
+
+void PnaclTranslationCache::OnCreateBackendComplete(int rv) {
+ if (rv < 0) {
+ LOG(ERROR) << "Backend init failed:" << net::ErrorToString(rv);
+ }
+ // Invoke our client's callback function.
+ if (!init_callback_.is_null()) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE, base::Bind(init_callback_, rv));
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+// High-level API
+
+void PnaclTranslationCache::StoreNexe(const std::string& key,
+ net::DrainableIOBuffer* nexe_data,
+ const CompletionCallback& callback) {
+ PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetWriteEntry(
+ AsWeakPtr(), key, nexe_data, callback);
+ open_entries_[entry] = entry;
+ entry->Start();
+}
+
+void PnaclTranslationCache::GetNexe(const std::string& key,
+ const GetNexeCallback& callback) {
+ PnaclTranslationCacheEntry* entry =
+ PnaclTranslationCacheEntry::GetReadEntry(AsWeakPtr(), key, callback);
+ open_entries_[entry] = entry;
+ entry->Start();
+}
+
+int PnaclTranslationCache::InitOnDisk(const base::FilePath& cache_directory,
+ const CompletionCallback& callback) {
+ in_memory_ = false;
+ return Init(net::PNACL_CACHE, cache_directory, 0 /* auto size */, callback);
+}
+
+int PnaclTranslationCache::InitInMemory(const CompletionCallback& callback) {
+ in_memory_ = true;
+ return Init(net::MEMORY_CACHE, base::FilePath(), kMaxMemCacheSize, callback);
+}
+
+int PnaclTranslationCache::Size() {
+ if (!disk_cache_)
+ return -1;
+ return disk_cache_->GetEntryCount();
+}
+
+// static
+std::string PnaclTranslationCache::GetKey(const nacl::PnaclCacheInfo& info) {
+ if (!info.pexe_url.is_valid() || info.abi_version < 0 || info.opt_level < 0)
+ return std::string();
+ std::string retval("ABI:");
+ retval += IntToString(info.abi_version) + ";" + "opt:" +
+ IntToString(info.opt_level) + ";" + "URL:";
+ // Filter the username, password, and ref components from the URL
+ GURL::Replacements replacements;
+ replacements.ClearUsername();
+ replacements.ClearPassword();
+ replacements.ClearRef();
+ GURL key_url(info.pexe_url.ReplaceComponents(replacements));
+ retval += key_url.spec() + ";";
+ // You would think that there is already code to format base::Time values
+ // somewhere, but I haven't found it yet. In any case, doing it ourselves
+ // here means we can keep the format stable.
+ base::Time::Exploded exploded;
+ info.last_modified.UTCExplode(&exploded);
+ if (info.last_modified.is_null() || !exploded.HasValidValues()) {
+ memset(&exploded, 0, sizeof(exploded));
+ }
+ retval += "modified:" + IntToString(exploded.year) + ":" +
+ IntToString(exploded.month) + ":" +
+ IntToString(exploded.day_of_month) + ":" +
+ IntToString(exploded.hour) + ":" + IntToString(exploded.minute) +
+ ":" + IntToString(exploded.second) + ":" +
+ IntToString(exploded.millisecond) + ":UTC;";
+ retval += "etag:" + info.etag;
+ return retval;
+}
+
+int PnaclTranslationCache::DoomEntriesBetween(
+ base::Time initial,
+ base::Time end,
+ const CompletionCallback& callback) {
+ return disk_cache_->DoomEntriesBetween(initial, end, callback);
+}
+
+} // namespace pnacl
diff --git a/components/nacl/browser/pnacl_translation_cache.h b/components/nacl/browser/pnacl_translation_cache.h
new file mode 100644
index 0000000..9dd528c
--- /dev/null
+++ b/components/nacl/browser/pnacl_translation_cache.h
@@ -0,0 +1,106 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_NACL_BROWSER_PNACL_TRANSLATION_CACHE_H_
+#define COMPONENTS_NACL_BROWSER_PNACL_TRANSLATION_CACHE_H_
+
+#include <map>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/cache_type.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace disk_cache {
+class Backend;
+}
+
+namespace nacl {
+struct PnaclCacheInfo;
+}
+
+namespace net {
+class DrainableIOBuffer;
+}
+
+namespace pnacl {
+typedef base::Callback<void(int)> CompletionCallback;
+typedef base::Callback<void(int, scoped_refptr<net::DrainableIOBuffer>)>
+ GetNexeCallback;
+class PnaclTranslationCacheEntry;
+extern const int kMaxMemCacheSize;
+
+class PnaclTranslationCache
+ : public base::SupportsWeakPtr<PnaclTranslationCache> {
+ public:
+ PnaclTranslationCache();
+ virtual ~PnaclTranslationCache();
+
+ // Initialize the translation cache in |cache_dir|. If the return value is
+ // net::ERR_IO_PENDING, |callback| will be called with a 0 argument on sucess
+ // and <0 otherwise.
+ int InitOnDisk(const base::FilePath& cache_dir,
+ const CompletionCallback& callback);
+
+ // Initialize the translation cache in memory. If the return value is
+ // net::ERR_IO_PENDING, |callback| will be called with a 0 argument on sucess
+ // and <0 otherwise.
+ int InitInMemory(const CompletionCallback& callback);
+
+ // Store the nexe in the translation cache, and call |callback| with
+ // the result. The result passed to the callback is 0 on success and
+ // <0 otherwise. A reference to |nexe_data| is held until completion
+ // or cancellation.
+ void StoreNexe(const std::string& key,
+ net::DrainableIOBuffer* nexe_data,
+ const CompletionCallback& callback);
+
+ // Retrieve the nexe from the translation cache. Write the data into |nexe|
+ // and call |callback|, passing a result code (0 on success and <0 otherwise),
+ // and a DrainableIOBuffer with the data.
+ void GetNexe(const std::string& key, const GetNexeCallback& callback);
+
+ // Return the number of entries in the cache backend.
+ int Size();
+
+ // Return the cache key for |info|
+ static std::string GetKey(const nacl::PnaclCacheInfo& info);
+
+ // Doom all entries between |initial| and |end|. If the return value is
+ // net::ERR_IO_PENDING, |callback| will be invoked when the operation
+ // completes.
+ int DoomEntriesBetween(base::Time initial, base::Time end,
+ const CompletionCallback& callback);
+
+ private:
+ friend class PnaclTranslationCacheEntry;
+ friend class PnaclTranslationCacheTest;
+ // PnaclTranslationCacheEntry should only use the
+ // OpComplete and backend methods on PnaclTranslationCache.
+ void OpComplete(PnaclTranslationCacheEntry* entry);
+ disk_cache::Backend* backend() { return disk_cache_.get(); }
+
+ int Init(net::CacheType,
+ const base::FilePath& directory,
+ int cache_size,
+ const CompletionCallback& callback);
+
+ void OnCreateBackendComplete(int rv);
+
+ scoped_ptr<disk_cache::Backend> disk_cache_;
+ CompletionCallback init_callback_;
+ bool in_memory_;
+ std::map<void*, scoped_refptr<PnaclTranslationCacheEntry> > open_entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(PnaclTranslationCache);
+};
+
+} // namespace pnacl
+
+#endif // COMPONENTS_NACL_BROWSER_PNACL_TRANSLATION_CACHE_H_
diff --git a/components/nacl/browser/pnacl_translation_cache_unittest.cc b/components/nacl/browser/pnacl_translation_cache_unittest.cc
new file mode 100644
index 0000000..94cd681
--- /dev/null
+++ b/components/nacl/browser/pnacl_translation_cache_unittest.cc
@@ -0,0 +1,269 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/nacl/browser/pnacl_translation_cache.h"
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "components/nacl/common/pnacl_types.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/base/io_buffer.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserThread;
+using base::FilePath;
+
+namespace pnacl {
+
+const int kTestDiskCacheSize = 16 * 1024 * 1024;
+
+class PnaclTranslationCacheTest : public testing::Test {
+ protected:
+ PnaclTranslationCacheTest()
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
+ virtual ~PnaclTranslationCacheTest() {}
+ virtual void SetUp() { cache_.reset(new PnaclTranslationCache()); }
+ virtual void TearDown() {
+ // The destructor of PnaclTranslationCacheWriteEntry posts a task to the IO
+ // thread to close the backend cache entry. We want to make sure the entries
+ // are closed before we delete the backend (and in particular the destructor
+ // for the memory backend has a DCHECK to verify this), so we run the loop
+ // here to ensure the task gets processed.
+ base::RunLoop().RunUntilIdle();
+ cache_.reset();
+ }
+
+ void InitBackend(bool in_mem);
+ void StoreNexe(const std::string& key, const std::string& nexe);
+ std::string GetNexe(const std::string& key);
+
+ scoped_ptr<PnaclTranslationCache> cache_;
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir temp_dir_;
+};
+
+void PnaclTranslationCacheTest::InitBackend(bool in_mem) {
+ net::TestCompletionCallback init_cb;
+ if (!in_mem) {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+ // Use the private init method so we can control the size
+ int rv = cache_->Init(in_mem ? net::MEMORY_CACHE : net::PNACL_CACHE,
+ temp_dir_.path(),
+ in_mem ? kMaxMemCacheSize : kTestDiskCacheSize,
+ init_cb.callback());
+ if (in_mem)
+ ASSERT_EQ(net::OK, rv);
+ ASSERT_EQ(net::OK, init_cb.GetResult(rv));
+ ASSERT_EQ(0, cache_->Size());
+}
+
+void PnaclTranslationCacheTest::StoreNexe(const std::string& key,
+ const std::string& nexe) {
+ net::TestCompletionCallback store_cb;
+ scoped_refptr<net::DrainableIOBuffer> nexe_buf(
+ new net::DrainableIOBuffer(new net::StringIOBuffer(nexe), nexe.size()));
+ cache_->StoreNexe(key, nexe_buf, store_cb.callback());
+ // Using ERR_IO_PENDING here causes the callback to wait for the result
+ // which should be harmless even if it returns OK immediately. This is because
+ // we don't plumb the intermediate writing stages all the way out.
+ EXPECT_EQ(net::OK, store_cb.GetResult(net::ERR_IO_PENDING));
+}
+
+// Inspired by net::TestCompletionCallback. Instantiate a TestNexeCallback and
+// pass the GetNexeCallback returned by the callback() method to GetNexe.
+// Then call GetResult, which will pump the message loop until it gets a result,
+// return the resulting IOBuffer and fill in the return value
+class TestNexeCallback {
+ public:
+ TestNexeCallback()
+ : have_result_(false),
+ result_(-1),
+ cb_(base::Bind(&TestNexeCallback::SetResult, base::Unretained(this))) {}
+ GetNexeCallback callback() { return cb_; }
+ net::DrainableIOBuffer* GetResult(int* result) {
+ while (!have_result_)
+ base::RunLoop().RunUntilIdle();
+ have_result_ = false;
+ *result = result_;
+ return buf_.get();
+ }
+
+ private:
+ void SetResult(int rv, scoped_refptr<net::DrainableIOBuffer> buf) {
+ have_result_ = true;
+ result_ = rv;
+ buf_ = buf;
+ }
+ bool have_result_;
+ int result_;
+ scoped_refptr<net::DrainableIOBuffer> buf_;
+ const GetNexeCallback cb_;
+};
+
+std::string PnaclTranslationCacheTest::GetNexe(const std::string& key) {
+ TestNexeCallback load_cb;
+ cache_->GetNexe(key, load_cb.callback());
+ int rv;
+ scoped_refptr<net::DrainableIOBuffer> buf(load_cb.GetResult(&rv));
+ EXPECT_EQ(net::OK, rv);
+ if (buf.get() == NULL) // for some reason ASSERT macros don't work here.
+ return std::string();
+ std::string nexe(buf->data(), buf->size());
+ return nexe;
+}
+
+static const std::string test_key("1");
+static const std::string test_store_val("testnexe");
+static const int kLargeNexeSize = 8 * 1024 * 1024;
+
+TEST(PnaclTranslationCacheKeyTest, CacheKeyTest) {
+ nacl::PnaclCacheInfo info;
+ info.pexe_url = GURL("http://www.google.com");
+ info.abi_version = 0;
+ info.opt_level = 0;
+ std::string test_time("Wed, 15 Nov 1995 06:25:24 GMT");
+ base::Time::FromString(test_time.c_str(), &info.last_modified);
+ // Basic check for URL and time components
+ EXPECT_EQ("ABI:0;opt:0;URL:http://www.google.com/;"
+ "modified:1995:11:15:6:25:24:0:UTC;etag:",
+ PnaclTranslationCache::GetKey(info));
+ // Check that query portion of URL is not stripped
+ info.pexe_url = GURL("http://www.google.com/?foo=bar");
+ EXPECT_EQ("ABI:0;opt:0;URL:http://www.google.com/?foo=bar;"
+ "modified:1995:11:15:6:25:24:0:UTC;etag:",
+ PnaclTranslationCache::GetKey(info));
+ // Check that username, password, and normal port are stripped
+ info.pexe_url = GURL("https://user:host@www.google.com:443/");
+ EXPECT_EQ("ABI:0;opt:0;URL:https://www.google.com/;"
+ "modified:1995:11:15:6:25:24:0:UTC;etag:",
+ PnaclTranslationCache::GetKey(info));
+ // Check that unusual port is not stripped but ref is stripped
+ info.pexe_url = GURL("https://www.google.com:444/#foo");
+ EXPECT_EQ("ABI:0;opt:0;URL:https://www.google.com:444/;"
+ "modified:1995:11:15:6:25:24:0:UTC;etag:",
+ PnaclTranslationCache::GetKey(info));
+ // Check chrome-extesnsion scheme
+ info.pexe_url = GURL("chrome-extension://ljacajndfccfgnfohlgkdphmbnpkjflk/");
+ EXPECT_EQ("ABI:0;opt:0;"
+ "URL:chrome-extension://ljacajndfccfgnfohlgkdphmbnpkjflk/;"
+ "modified:1995:11:15:6:25:24:0:UTC;etag:",
+ PnaclTranslationCache::GetKey(info));
+ // Check that ABI version, opt level, and etag are in the key
+ info.pexe_url = GURL("http://www.google.com/");
+ info.abi_version = 2;
+ EXPECT_EQ("ABI:2;opt:0;URL:http://www.google.com/;"
+ "modified:1995:11:15:6:25:24:0:UTC;etag:",
+ PnaclTranslationCache::GetKey(info));
+ info.opt_level = 2;
+ EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;"
+ "modified:1995:11:15:6:25:24:0:UTC;etag:",
+ PnaclTranslationCache::GetKey(info));
+ info.etag = std::string("etag");
+ EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;"
+ "modified:1995:11:15:6:25:24:0:UTC;etag:etag",
+ PnaclTranslationCache::GetKey(info));
+
+ // Check for all the time components, and null time
+ info.last_modified = base::Time();
+ EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;"
+ "modified:0:0:0:0:0:0:0:UTC;etag:etag",
+ PnaclTranslationCache::GetKey(info));
+ test_time.assign("Fri, 29 Feb 2008 13:04:12 GMT");
+ base::Time::FromString(test_time.c_str(), &info.last_modified);
+ EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;"
+ "modified:2008:2:29:13:4:12:0:UTC;etag:etag",
+ PnaclTranslationCache::GetKey(info));
+}
+
+TEST_F(PnaclTranslationCacheTest, StoreSmallInMem) {
+ // Test that a single store puts something in the mem backend
+ InitBackend(true);
+ StoreNexe(test_key, test_store_val);
+ EXPECT_EQ(1, cache_->Size());
+}
+
+TEST_F(PnaclTranslationCacheTest, StoreSmallOnDisk) {
+ // Test that a single store puts something in the disk backend
+ InitBackend(false);
+ StoreNexe(test_key, test_store_val);
+ EXPECT_EQ(1, cache_->Size());
+}
+
+TEST_F(PnaclTranslationCacheTest, StoreLargeOnDisk) {
+ // Test a value too large(?) for a single I/O operation
+ InitBackend(false);
+ const std::string large_buffer(kLargeNexeSize, 'a');
+ StoreNexe(test_key, large_buffer);
+ EXPECT_EQ(1, cache_->Size());
+}
+
+TEST_F(PnaclTranslationCacheTest, InMemSizeLimit) {
+ InitBackend(true);
+ scoped_refptr<net::DrainableIOBuffer> large_buffer(new net::DrainableIOBuffer(
+ new net::StringIOBuffer(std::string(kMaxMemCacheSize + 1, 'a')),
+ kMaxMemCacheSize + 1));
+ net::TestCompletionCallback store_cb;
+ cache_->StoreNexe(test_key, large_buffer, store_cb.callback());
+ EXPECT_EQ(net::ERR_FAILED, store_cb.GetResult(net::ERR_IO_PENDING));
+ base::RunLoop().RunUntilIdle(); // Ensure the entry is closed.
+ EXPECT_EQ(0, cache_->Size());
+}
+
+TEST_F(PnaclTranslationCacheTest, GetOneInMem) {
+ InitBackend(true);
+ StoreNexe(test_key, test_store_val);
+ EXPECT_EQ(1, cache_->Size());
+ EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val));
+}
+
+TEST_F(PnaclTranslationCacheTest, GetOneOnDisk) {
+ InitBackend(false);
+ StoreNexe(test_key, test_store_val);
+ EXPECT_EQ(1, cache_->Size());
+ EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val));
+}
+
+TEST_F(PnaclTranslationCacheTest, GetLargeOnDisk) {
+ InitBackend(false);
+ const std::string large_buffer(kLargeNexeSize, 'a');
+ StoreNexe(test_key, large_buffer);
+ EXPECT_EQ(1, cache_->Size());
+ EXPECT_EQ(0, GetNexe(test_key).compare(large_buffer));
+}
+
+TEST_F(PnaclTranslationCacheTest, StoreTwice) {
+ // Test that storing twice with the same key overwrites
+ InitBackend(true);
+ StoreNexe(test_key, test_store_val);
+ StoreNexe(test_key, test_store_val + "aaa");
+ EXPECT_EQ(1, cache_->Size());
+ EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val + "aaa"));
+}
+
+TEST_F(PnaclTranslationCacheTest, StoreTwo) {
+ InitBackend(true);
+ StoreNexe(test_key, test_store_val);
+ StoreNexe(test_key + "a", test_store_val + "aaa");
+ EXPECT_EQ(2, cache_->Size());
+ EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val));
+ EXPECT_EQ(0, GetNexe(test_key + "a").compare(test_store_val + "aaa"));
+}
+
+TEST_F(PnaclTranslationCacheTest, GetMiss) {
+ InitBackend(true);
+ StoreNexe(test_key, test_store_val);
+ TestNexeCallback load_cb;
+ std::string nexe;
+ cache_->GetNexe(test_key + "a", load_cb.callback());
+ int rv;
+ scoped_refptr<net::DrainableIOBuffer> buf(load_cb.GetResult(&rv));
+ EXPECT_EQ(net::ERR_FAILED, rv);
+}
+
+} // namespace pnacl
diff --git a/components/nacl_common.gyp b/components/nacl_common.gyp
index cedf675..960eb1c 100644
--- a/components/nacl_common.gyp
+++ b/components/nacl_common.gyp
@@ -25,6 +25,8 @@
'nacl/common/nacl_messages.h',
'nacl/common/nacl_types.cc',
'nacl/common/nacl_types.h',
+ 'nacl/common/pnacl_types.cc',
+ 'nacl/common/pnacl_types.h',
],
'include_dirs': [
'..',