summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrkc@chromium.org <rkc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-07 07:06:18 +0000
committerrkc@chromium.org <rkc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-07 07:06:18 +0000
commitfa84b0d4c75afaac54a0616c3bd0efb9cbb68537 (patch)
tree683f6add90744be002ef3aca1e25600a3e7541f9
parent937c710fffdcd8e04827dbf58fc273f377267661 (diff)
downloadchromium_src-fa84b0d4c75afaac54a0616c3bd0efb9cbb68537.zip
chromium_src-fa84b0d4c75afaac54a0616c3bd0efb9cbb68537.tar.gz
chromium_src-fa84b0d4c75afaac54a0616c3bd0efb9cbb68537.tar.bz2
Add the whispernet proxy.
To communicate with whispernet (which needs to be checked in as a pNacl binary), the only way possible is for us to go through an extension. This code implements the ChromeWhispernetClient, which uses the whispernet proxy compoent extension. This extension is implemented on top of a private API that allows it to communicate back and forth with the ChromeWhispernetClient. The tests for this code exist only for the ChromeWhispernetClient since they excersize the full flow back and forth from sending a request to encode a token to receiving the samples, and then decoding samples and receiving the correct token. This is our temporary measure for M38 till we can find a better way to communicate with the whispernet library. Owners reviews requested: xiyuan@ - For the overall review + the whispernet proxy code in c/b/r/whispernet_proxy jochen@ - For adding components/copresence to c/b/DEPS kalman@ - For the copresence_private API ben@ - For adding the c/b/copresence directory and the OWNERS file + the addition to c/b/browser_resources.grd R=ben@chromium.org, jochen@chromium.org, kalman@chromium.org, xiyuan@chromium.org BUG=365493 Review URL: https://codereview.chromium.org/438513002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287976 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/DEPS1
-rw-r--r--chrome/browser/browser_resources.grd1
-rw-r--r--chrome/browser/copresence/OWNERS3
-rw-r--r--chrome/browser/copresence/chrome_whispernet_client.cc200
-rw-r--r--chrome/browser/copresence/chrome_whispernet_client.h84
-rw-r--r--chrome/browser/copresence/chrome_whispernet_client_browsertest.cc179
-rw-r--r--chrome/browser/extensions/api/copresence_private/copresence_private_api.cc105
-rw-r--r--chrome/browser/extensions/api/copresence_private/copresence_private_api.h69
-rw-r--r--chrome/browser/resources/component_extension_resources.grd11
-rw-r--r--chrome/browser/resources/whispernet_proxy/OWNERS3
-rw-r--r--chrome/browser/resources/whispernet_proxy/background.html11
-rw-r--r--chrome/browser/resources/whispernet_proxy/js/init.js104
-rw-r--r--chrome/browser/resources/whispernet_proxy/js/nacl.js102
-rw-r--r--chrome/browser/resources/whispernet_proxy/js/wrapper.js224
-rw-r--r--chrome/browser/resources/whispernet_proxy/manifest.json13
-rw-r--r--chrome/chrome_browser_extensions.gypi4
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/extensions/api/_api_features.json4
-rw-r--r--chrome/common/extensions/api/_permission_features.json8
-rw-r--r--chrome/common/extensions/api/api.gyp1
-rw-r--r--chrome/common/extensions/api/copresence_private.idl49
-rw-r--r--chrome/common/extensions/permissions/chrome_api_permissions.cc1
-rw-r--r--chrome/common/extensions/permissions/permission_set_unittest.cc1
-rw-r--r--extensions/browser/extension_function_histogram_value.h4
-rw-r--r--extensions/common/permissions/api_permission.h1
-rw-r--r--tools/metrics/histograms/histograms.xml4
26 files changed, 1188 insertions, 0 deletions
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 797e41f..6b4f26e 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -20,6 +20,7 @@ include_rules = [
"+components/cdm/browser",
"+components/cloud_devices/common",
"+components/content_settings",
+ "+components/copresence",
"+components/data_reduction_proxy",
"+components/dom_distiller",
"+components/domain_reliability",
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index d2b7ed8..0e975f7 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -436,6 +436,7 @@
<include name="IDR_HANGUL_MANIFEST" file="resources\chromeos\input_method\hangul_manifest.json" type="BINDATA" />
<include name="IDR_BRAILLE_MANIFEST" file="resources\chromeos\braille_ime\manifest.json" type="BINDATA" />
</if>
+ <include name="IDR_WHISPERNET_PROXY_MANIFEST" file="resources\whispernet_proxy\manifest.json" type="BINDATA" />
</includes>
</release>
</grit>
diff --git a/chrome/browser/copresence/OWNERS b/chrome/browser/copresence/OWNERS
new file mode 100644
index 0000000..2bc8d07
--- /dev/null
+++ b/chrome/browser/copresence/OWNERS
@@ -0,0 +1,3 @@
+rkc@chromium.org
+ckehoe@chromium.org
+xiyuan@chromium.org
diff --git a/chrome/browser/copresence/chrome_whispernet_client.cc b/chrome/browser/copresence/chrome_whispernet_client.cc
new file mode 100644
index 0000000..3fa8bbb
--- /dev/null
+++ b/chrome/browser/copresence/chrome_whispernet_client.cc
@@ -0,0 +1,200 @@
+// Copyright 2014 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 "chrome/browser/copresence/chrome_whispernet_client.h"
+
+#include "chrome/browser/extensions/api/copresence_private/copresence_private_api.h"
+#include "chrome/browser/extensions/component_loader.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/common/extensions/api/copresence_private.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_system.h"
+#include "grit/browser_resources.h"
+
+namespace copresence {
+
+// Once the CL for the copresence component lands, these constants will be
+// need to be picked up from components/copresence/copresence_constants.h
+const int kDefaultRepetitions = 5;
+const float kDefaultSampleRate = 48000.0;
+const int kDefaultBitsPerSample = 16;
+const float kDefaultCarrierFrequency = 18500.0;
+const int kDefaultChannels = 2;
+
+} // namespace copresence
+
+// static
+const char ChromeWhispernetClient::kWhispernetProxyExtensionId[] =
+ "bpfmnplchembfbdgieamdodgaencleal";
+
+// Public:
+
+ChromeWhispernetClient::ChromeWhispernetClient(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context), extension_loaded_(false) {
+}
+
+ChromeWhispernetClient::~ChromeWhispernetClient() {
+}
+
+void ChromeWhispernetClient::Initialize(const SuccessCallback& init_callback) {
+ DVLOG(3) << "Initializing whispernet proxy client.";
+ init_callback_ = init_callback;
+
+ extensions::ExtensionSystem* es =
+ extensions::ExtensionSystem::Get(browser_context_);
+ DCHECK(es);
+ ExtensionService* service = es->extension_service();
+ DCHECK(service);
+ extensions::ComponentLoader* loader = service->component_loader();
+ DCHECK(loader);
+
+ // This callback is cancelled in Shutdown().
+ extension_loaded_callback_ = base::Bind(
+ &ChromeWhispernetClient::OnExtensionLoaded, base::Unretained(this));
+
+ if (!loader->Exists(kWhispernetProxyExtensionId)) {
+ DVLOG(3) << "Loading Whispernet proxy.";
+ loader->Add(IDR_WHISPERNET_PROXY_MANIFEST,
+ base::FilePath(FILE_PATH_LITERAL("whispernet_proxy")));
+ } else {
+ init_callback_.Run(true);
+ }
+}
+
+void ChromeWhispernetClient::Shutdown() {
+ extension_loaded_callback_.Reset();
+ init_callback_.Reset();
+ tokens_callback_.Reset();
+ samples_callback_.Reset();
+ db_callback_.Reset();
+}
+
+// Fire an event to request a token encode.
+void ChromeWhispernetClient::EncodeToken(const std::string& token) {
+ DCHECK(extension_loaded_);
+ DCHECK(browser_context_);
+ DCHECK(extensions::EventRouter::Get(browser_context_));
+
+ scoped_ptr<extensions::Event> event(new extensions::Event(
+ extensions::api::copresence_private::OnEncodeTokenRequest::kEventName,
+ extensions::api::copresence_private::OnEncodeTokenRequest::Create(token),
+ browser_context_));
+
+ extensions::EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(kWhispernetProxyExtensionId, event.Pass());
+}
+
+// Fire an event to request a decode for the given samples.
+void ChromeWhispernetClient::DecodeSamples(const std::string& samples) {
+ DCHECK(extension_loaded_);
+ DCHECK(browser_context_);
+ DCHECK(extensions::EventRouter::Get(browser_context_));
+
+ scoped_ptr<extensions::Event> event(new extensions::Event(
+ extensions::api::copresence_private::OnDecodeSamplesRequest::kEventName,
+ extensions::api::copresence_private::OnDecodeSamplesRequest::Create(
+ samples),
+ browser_context_));
+
+ extensions::EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(kWhispernetProxyExtensionId, event.Pass());
+}
+
+void ChromeWhispernetClient::DetectBroadcast() {
+ DCHECK(extension_loaded_);
+ DCHECK(browser_context_);
+ DCHECK(extensions::EventRouter::Get(browser_context_));
+
+ scoped_ptr<extensions::Event> event(new extensions::Event(
+ extensions::api::copresence_private::OnDetectBroadcastRequest::kEventName,
+ make_scoped_ptr(new base::ListValue()),
+ browser_context_));
+
+ extensions::EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(kWhispernetProxyExtensionId, event.Pass());
+}
+
+void ChromeWhispernetClient::RegisterTokensCallback(
+ const TokensCallback& tokens_callback) {
+ tokens_callback_ = tokens_callback;
+}
+
+void ChromeWhispernetClient::RegisterSamplesCallback(
+ const SamplesCallback& samples_callback) {
+ samples_callback_ = samples_callback;
+}
+
+void ChromeWhispernetClient::RegisterDetectBroadcastCallback(
+ const SuccessCallback& db_callback) {
+ db_callback_ = db_callback;
+}
+
+ChromeWhispernetClient::TokensCallback
+ChromeWhispernetClient::GetTokensCallback() {
+ return tokens_callback_;
+}
+
+ChromeWhispernetClient::SamplesCallback
+ChromeWhispernetClient::GetSamplesCallback() {
+ return samples_callback_;
+}
+
+ChromeWhispernetClient::SuccessCallback
+ChromeWhispernetClient::GetDetectBroadcastCallback() {
+ return db_callback_;
+}
+
+ChromeWhispernetClient::SuccessCallback
+ChromeWhispernetClient::GetInitializedCallback() {
+ return extension_loaded_callback_;
+}
+
+// Private:
+
+// Fire an event to initialize whispernet with the given parameters.
+void ChromeWhispernetClient::InitializeWhispernet(
+ const extensions::api::copresence_private::AudioParameters& params) {
+ DCHECK(browser_context_);
+ DCHECK(extensions::EventRouter::Get(browser_context_));
+
+ scoped_ptr<extensions::Event> event(new extensions::Event(
+ extensions::api::copresence_private::OnInitialize::kEventName,
+ extensions::api::copresence_private::OnInitialize::Create(params),
+ browser_context_));
+
+ extensions::EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(kWhispernetProxyExtensionId, event.Pass());
+}
+
+void ChromeWhispernetClient::OnExtensionLoaded(bool success) {
+ if (extension_loaded_) {
+ if (!init_callback_.is_null())
+ init_callback_.Run(success);
+ return;
+ }
+
+ // Our extension just got loaded, initialize whispernet.
+ extension_loaded_ = true;
+
+ // This will fire another OnExtensionLoaded call once the initialization is
+ // done, which means we've initialized for realz, hence call the init
+ // callback.
+
+ // At this point, we have the same parameters for record and play. This
+ // may change later though (ongoing discussion with research).
+ extensions::api::copresence_private::AudioParameters params;
+ params.play.sample_rate = copresence::kDefaultSampleRate;
+ params.play.bits_per_sample = copresence::kDefaultBitsPerSample;
+ params.play.carrier_frequency = copresence::kDefaultCarrierFrequency;
+ params.play.repetitions = copresence::kDefaultRepetitions;
+
+ params.record.sample_rate = copresence::kDefaultSampleRate;
+ params.record.bits_per_sample = copresence::kDefaultBitsPerSample;
+ params.record.carrier_frequency = copresence::kDefaultCarrierFrequency;
+ params.record.channels = copresence::kDefaultChannels;
+
+ InitializeWhispernet(params);
+}
diff --git a/chrome/browser/copresence/chrome_whispernet_client.h b/chrome/browser/copresence/chrome_whispernet_client.h
new file mode 100644
index 0000000..a644a1a
--- /dev/null
+++ b/chrome/browser/copresence/chrome_whispernet_client.h
@@ -0,0 +1,84 @@
+// Copyright 2014 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 CHROME_BROWSER_COPRESENCE_CHROME_WHISPERNET_CLIENT_H_
+#define CHROME_BROWSER_COPRESENCE_CHROME_WHISPERNET_CLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "components/copresence/public/whispernet_client.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+namespace api {
+namespace copresence_private {
+struct AudioParameters;
+}
+}
+}
+
+namespace media {
+class AudioBusRefCounted;
+}
+
+// This class is responsible for communication with our ledger_proxy extension
+// that talks to the whispernet audio library.
+class ChromeWhispernetClient : public copresence::WhispernetClient {
+ public:
+ // The browser context needs to outlive this class.
+ explicit ChromeWhispernetClient(content::BrowserContext* browser_context);
+ virtual ~ChromeWhispernetClient();
+
+ // WhispernetClient overrides:
+ virtual void Initialize(const SuccessCallback& init_callback) OVERRIDE;
+ virtual void Shutdown() OVERRIDE;
+
+ virtual void EncodeToken(const std::string& token) OVERRIDE;
+ virtual void DecodeSamples(const std::string& samples) OVERRIDE;
+ virtual void DetectBroadcast() OVERRIDE;
+
+ virtual void RegisterTokensCallback(
+ const TokensCallback& tokens_callback) OVERRIDE;
+ virtual void RegisterSamplesCallback(
+ const SamplesCallback& samples_callback) OVERRIDE;
+ virtual void RegisterDetectBroadcastCallback(
+ const SuccessCallback& db_callback) OVERRIDE;
+
+ virtual TokensCallback GetTokensCallback() OVERRIDE;
+ virtual SamplesCallback GetSamplesCallback() OVERRIDE;
+ virtual SuccessCallback GetDetectBroadcastCallback() OVERRIDE;
+ virtual SuccessCallback GetInitializedCallback() OVERRIDE;
+
+ static const char kWhispernetProxyExtensionId[];
+
+ private:
+ // Fire an event to initialize whispernet with the given parameters.
+ void InitializeWhispernet(
+ const extensions::api::copresence_private::AudioParameters& params);
+
+ // This gets called twice; once when the proxy extension loads, the second
+ // time when we have initialized the proxy extension's encoder and decoder.
+ void OnExtensionLoaded(bool success);
+
+ content::BrowserContext* browser_context_;
+
+ SuccessCallback extension_loaded_callback_;
+ SuccessCallback init_callback_;
+
+ TokensCallback tokens_callback_;
+ SamplesCallback samples_callback_;
+ SuccessCallback db_callback_;
+
+ bool extension_loaded_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeWhispernetClient);
+};
+
+#endif // CHROME_BROWSER_COPRESENCE_CHROME_WHISPERNET_CLIENT_H_
diff --git a/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc b/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc
new file mode 100644
index 0000000..469a42d
--- /dev/null
+++ b/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc
@@ -0,0 +1,179 @@
+// Copyright 2014 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 "chrome/browser/copresence/chrome_whispernet_client.h"
+
+#include <cstdlib>
+#include <string>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "chrome/browser/extensions/api/copresence_private/copresence_private_api.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "media/base/audio_bus.h"
+
+namespace {
+
+// Copied from src/components/copresence/mediums/audio/audio_recorder.cc
+std::string AudioBusToString(scoped_refptr<media::AudioBusRefCounted> source) {
+ std::string buffer;
+ buffer.resize(source->frames() * source->channels() * sizeof(float));
+ float* buffer_view = reinterpret_cast<float*>(string_as_array(&buffer));
+
+ const int channels = source->channels();
+ for (int ch = 0; ch < channels; ++ch) {
+ for (int si = 0, di = ch; si < source->frames(); ++si, di += channels)
+ buffer_view[di] = source->channel(ch)[si];
+ }
+
+ return buffer;
+}
+
+} // namespace
+
+class ChromeWhispernetClientTest : public ExtensionBrowserTest {
+ public:
+ ChromeWhispernetClientTest() : initialized_(false) {}
+
+ virtual ~ChromeWhispernetClientTest() {
+ if (client_)
+ extensions::SetWhispernetClientForTesting(NULL);
+ }
+
+ void InitializeWhispernet() {
+ run_loop_.reset(new base::RunLoop());
+ client_.reset(new ChromeWhispernetClient(browser()->profile()));
+ extensions::SetWhispernetClientForTesting(client_.get());
+
+ client_->Initialize(base::Bind(&ChromeWhispernetClientTest::InitCallback,
+ base::Unretained(this)));
+ run_loop_->Run();
+
+ EXPECT_TRUE(initialized_);
+ }
+
+ void EncodeTokenAndSaveSamples() {
+ ASSERT_TRUE(client_);
+
+ // This is the base64 encoding for 000000.
+ const std::string kZeroToken = "MDAwMDAw";
+
+ run_loop_.reset(new base::RunLoop());
+ client_->RegisterSamplesCallback(base::Bind(
+ &ChromeWhispernetClientTest::SamplesCallback, base::Unretained(this)));
+ expected_token_ = kZeroToken;
+
+ client_->EncodeToken(kZeroToken);
+ run_loop_->Run();
+
+ EXPECT_GT(saved_samples_->frames(), 0);
+ }
+
+ void DecodeSamplesAndVerifyToken() {
+ ASSERT_TRUE(client_);
+
+ const std::string kZeroToken = "MDAwMDAw";
+
+ run_loop_.reset(new base::RunLoop());
+ client_->RegisterTokensCallback(base::Bind(
+ &ChromeWhispernetClientTest::TokensCallback, base::Unretained(this)));
+ expected_token_ = kZeroToken;
+
+ ASSERT_GT(saved_samples_->frames(), 0);
+
+ // Convert our single channel samples to two channel. Decode samples
+ // expects 2 channel data.
+ scoped_refptr<media::AudioBusRefCounted> samples_bus =
+ media::AudioBusRefCounted::Create(2, saved_samples_->frames());
+ memcpy(samples_bus->channel(0),
+ saved_samples_->channel(0),
+ sizeof(float) * saved_samples_->frames());
+ memcpy(samples_bus->channel(1),
+ saved_samples_->channel(0),
+ sizeof(float) * saved_samples_->frames());
+
+ client_->DecodeSamples(AudioBusToString(samples_bus));
+ run_loop_->Run();
+ }
+
+ void DetectBroadcast() {
+ ASSERT_TRUE(client_);
+
+ run_loop_.reset(new base::RunLoop());
+ client_->RegisterDetectBroadcastCallback(
+ base::Bind(&ChromeWhispernetClientTest::DetectBroadcastCallback,
+ base::Unretained(this)));
+ client_->DetectBroadcast();
+ run_loop_->Run();
+ }
+
+ protected:
+ void InitCallback(bool success) {
+ EXPECT_TRUE(success);
+ initialized_ = true;
+ ASSERT_TRUE(run_loop_);
+ run_loop_->Quit();
+ }
+
+ void SamplesCallback(
+ const std::string& token,
+ const scoped_refptr<media::AudioBusRefCounted>& samples) {
+ EXPECT_EQ(expected_token_, token);
+ saved_samples_ = samples;
+ ASSERT_TRUE(run_loop_);
+ run_loop_->Quit();
+ }
+
+ void TokensCallback(const std::vector<std::string>& tokens) {
+ ASSERT_TRUE(run_loop_);
+ run_loop_->Quit();
+
+ EXPECT_EQ(expected_token_, tokens[0]);
+ }
+
+ void DetectBroadcastCallback(bool success) {
+ EXPECT_TRUE(success);
+ ASSERT_TRUE(run_loop_);
+ run_loop_->Quit();
+ }
+
+ private:
+ scoped_ptr<base::RunLoop> run_loop_;
+ scoped_ptr<ChromeWhispernetClient> client_;
+
+ std::string expected_token_;
+ scoped_refptr<media::AudioBusRefCounted> saved_samples_;
+
+ bool initialized_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeWhispernetClientTest);
+};
+
+IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, Initialize) {
+ InitializeWhispernet();
+}
+
+IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, EncodeToken) {
+ InitializeWhispernet();
+ EncodeTokenAndSaveSamples();
+}
+
+IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, DecodeSamples) {
+ InitializeWhispernet();
+ EncodeTokenAndSaveSamples();
+ DecodeSamplesAndVerifyToken();
+}
+
+IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, DetectBroadcast) {
+ InitializeWhispernet();
+ EncodeTokenAndSaveSamples();
+ DecodeSamplesAndVerifyToken();
+ DetectBroadcast();
+}
diff --git a/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc b/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc
new file mode 100644
index 0000000..2dcc52e
--- /dev/null
+++ b/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc
@@ -0,0 +1,105 @@
+// Copyright 2014 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 "chrome/browser/extensions/api/copresence_private/copresence_private_api.h"
+
+#include "base/lazy_instance.h"
+#include "base/stl_util.h"
+#include "chrome/browser/copresence/chrome_whispernet_client.h"
+#include "chrome/common/extensions/api/copresence_private.h"
+#include "components/copresence/public/whispernet_client.h"
+#include "media/base/audio_bus.h"
+
+namespace extensions {
+
+// This code is only for testing while we don't have the rest of the
+// CopresenceAPI service which will actually give us the whispernet client.
+// Once we add that code, both the g_whispernet_client and the
+// GetWhispernetClient function will go away, to be replaced by the
+// GetWhispernetClient function that will fetch our active whispernet client
+// from the CopresenceAPI profile keyed service.
+copresence::WhispernetClient* g_whispernet_client = NULL;
+
+// Copresence Private functions.
+
+copresence::WhispernetClient* CopresencePrivateFunction::GetWhispernetClient() {
+ // This is temporary code, this needs to be replaced by the real
+ // GetWhispernetClient code from c/b/e/api/copresence/copresence_util.h
+ DCHECK(g_whispernet_client);
+ return g_whispernet_client;
+}
+
+// CopresenceSendFoundFunction implementation:
+ExtensionFunction::ResponseAction CopresencePrivateSendFoundFunction::Run() {
+ if (!GetWhispernetClient() ||
+ GetWhispernetClient()->GetTokensCallback().is_null()) {
+ return RespondNow(NoArguments());
+ }
+
+ scoped_ptr<api::copresence_private::SendFound::Params> params(
+ api::copresence_private::SendFound::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ GetWhispernetClient()->GetTokensCallback().Run(params->tokens);
+ return RespondNow(NoArguments());
+}
+
+// CopresenceSendEncodedFunction implementation:
+ExtensionFunction::ResponseAction CopresencePrivateSendSamplesFunction::Run() {
+ if (!GetWhispernetClient() ||
+ GetWhispernetClient()->GetSamplesCallback().is_null()) {
+ return RespondNow(NoArguments());
+ }
+
+ scoped_ptr<api::copresence_private::SendSamples::Params> params(
+ api::copresence_private::SendSamples::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ scoped_refptr<media::AudioBusRefCounted> samples =
+ media::AudioBusRefCounted::Create(1,
+ params->samples.size() / sizeof(float));
+
+ memcpy(samples->channel(0),
+ string_as_array(&params->samples),
+ params->samples.size());
+
+ GetWhispernetClient()->GetSamplesCallback().Run(params->token, samples);
+ return RespondNow(NoArguments());
+}
+
+// CopresenceSendDetectFunction implementation:
+ExtensionFunction::ResponseAction CopresencePrivateSendDetectFunction::Run() {
+ if (!GetWhispernetClient() ||
+ GetWhispernetClient()->GetDetectBroadcastCallback().is_null()) {
+ return RespondNow(NoArguments());
+ }
+
+ scoped_ptr<api::copresence_private::SendDetect::Params> params(
+ api::copresence_private::SendDetect::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ GetWhispernetClient()->GetDetectBroadcastCallback().Run(params->detected);
+ return RespondNow(NoArguments());
+}
+
+// CopresenceSendInitializedFunction implementation:
+ExtensionFunction::ResponseAction
+CopresencePrivateSendInitializedFunction::Run() {
+ if (!GetWhispernetClient() ||
+ GetWhispernetClient()->GetInitializedCallback().is_null()) {
+ return RespondNow(NoArguments());
+ }
+
+ scoped_ptr<api::copresence_private::SendInitialized::Params> params(
+ api::copresence_private::SendInitialized::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ GetWhispernetClient()->GetInitializedCallback().Run(params->success);
+ return RespondNow(NoArguments());
+}
+
+void SetWhispernetClientForTesting(copresence::WhispernetClient* client) {
+ g_whispernet_client = client;
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/copresence_private/copresence_private_api.h b/chrome/browser/extensions/api/copresence_private/copresence_private_api.h
new file mode 100644
index 0000000..2eb0265
--- /dev/null
+++ b/chrome/browser/extensions/api/copresence_private/copresence_private_api.h
@@ -0,0 +1,69 @@
+// Copyright 2014 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 CHROME_BROWSER_EXTENSIONS_API_COPRESENCE_PRIVATE_COPRESENCE_PRIVATE_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_COPRESENCE_PRIVATE_COPRESENCE_PRIVATE_API_H_
+
+#include "chrome/browser/extensions/chrome_extension_function.h"
+
+namespace copresence {
+class WhispernetClient;
+}
+
+namespace extensions {
+
+class CopresencePrivateFunction : public ChromeUIThreadExtensionFunction {
+ protected:
+ copresence::WhispernetClient* GetWhispernetClient();
+ virtual ~CopresencePrivateFunction() {}
+};
+
+class CopresencePrivateSendFoundFunction : public CopresencePrivateFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendFound",
+ COPRESENCEPRIVATE_SENDFOUND);
+
+ protected:
+ virtual ~CopresencePrivateSendFoundFunction() {}
+ virtual ExtensionFunction::ResponseAction Run() OVERRIDE;
+};
+
+class CopresencePrivateSendSamplesFunction : public CopresencePrivateFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendSamples",
+ COPRESENCEPRIVATE_SENDSAMPLES);
+
+ protected:
+ virtual ~CopresencePrivateSendSamplesFunction() {}
+ virtual ExtensionFunction::ResponseAction Run() OVERRIDE;
+};
+
+class CopresencePrivateSendDetectFunction : public CopresencePrivateFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendDetect",
+ COPRESENCEPRIVATE_SENDDETECT);
+
+ protected:
+ virtual ~CopresencePrivateSendDetectFunction() {}
+ virtual ExtensionFunction::ResponseAction Run() OVERRIDE;
+};
+
+class CopresencePrivateSendInitializedFunction
+ : public CopresencePrivateFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendInitialized",
+ COPRESENCEPRIVATE_SENDINITIALIZED);
+
+ protected:
+ virtual ~CopresencePrivateSendInitializedFunction() {}
+ virtual ExtensionFunction::ResponseAction Run() OVERRIDE;
+};
+
+// This will go away once we check in the code for the CopresenceAPI BCK
+// service which lets us inject a whispernet client.
+void SetWhispernetClientForTesting(copresence::WhispernetClient* client);
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_COPRESENCE_PRIVATE_COPRESENCE_PRIVATE_API_H_
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index 8c6d093d..c2608f2 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -172,6 +172,17 @@
<include name="IDR_LEDGER_LEDGER_PROXY_NMF" file="../extensions/api/ledger/ledger_proxy/ledger_proxy.nmf.js" type="BINDATA" />
<include name="IDR_LEDGER_LEDGER_PROXY_PEXE" file="../extensions/api/ledger/ledger_proxy/ledger_proxy_pnacl.pexe.js" type="BINDATA" />
</if>
+ <include name="IDR_WHISPERNET_PROXY_BACKGROUND_HTML" file="whispernet_proxy/background.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+ <include name="IDR_WHISPERNET_PROXY_INIT_JS" file="whispernet_proxy/js/init.js" type="BINDATA" />
+ <include name="IDR_WHISPERNET_PROXY_NACL_JS" file="whispernet_proxy/js/nacl.js" type="BINDATA" />
+ <include name="IDR_WHISPERNET_PROXY_WRAPPER_JS" file="whispernet_proxy/js/wrapper.js" type="BINDATA" />
+ <!-- The next two files have a .png extension since grit for some reason
+ won't compile these files in if they are named .nmf/.pexe or
+ anything remotely similar to their actual extension. Naming these
+ .js or .html breaks all kinds of presubmit checks, hence .png seems
+ to be the only viable option at the moment. -->
+ <include name="IDR_WHISPERNET_PROXY_WHISPERNET_PROXY_PROXY_NMF" file="whispernet_proxy/whispernet_proxy.nmf.png" type="BINDATA" />
+ <include name="IDR_WHISPERNET_PROXY_WHISPERNET_PROXY_PROXY_PEXE" file="whispernet_proxy/whispernet_proxy_pnacl.pexe.png" type="BINDATA" />
</includes>
</release>
</grit>
diff --git a/chrome/browser/resources/whispernet_proxy/OWNERS b/chrome/browser/resources/whispernet_proxy/OWNERS
new file mode 100644
index 0000000..2bc8d07
--- /dev/null
+++ b/chrome/browser/resources/whispernet_proxy/OWNERS
@@ -0,0 +1,3 @@
+rkc@chromium.org
+ckehoe@chromium.org
+xiyuan@chromium.org
diff --git a/chrome/browser/resources/whispernet_proxy/background.html b/chrome/browser/resources/whispernet_proxy/background.html
new file mode 100644
index 0000000..06a8476
--- /dev/null
+++ b/chrome/browser/resources/whispernet_proxy/background.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="js/nacl.js"></script>
+<script src="js/wrapper.js"></script>
+<script src="js/init.js"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/chrome/browser/resources/whispernet_proxy/js/init.js b/chrome/browser/resources/whispernet_proxy/js/init.js
new file mode 100644
index 0000000..e346f206
--- /dev/null
+++ b/chrome/browser/resources/whispernet_proxy/js/init.js
@@ -0,0 +1,104 @@
+// Copyright 2014 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.
+
+'use strict';
+
+// Globals holding our encoder and decoder. We will never have more than one
+// Global variable that will be used to access this Nacl bridge.
+var whispernetNacl = null;
+
+// copy of an encoder or a decoder at a time.
+var whisperEncoder = null;
+var whisperDecoder = null;
+
+/**
+ * Initialize the whispernet encoder and decoder.
+ * @param {Object} audioParams Audio parameters used to initialize the encoder
+ * and decoder.
+ */
+function initialize(audioParams) {
+ if (!whispernetNacl) {
+ chrome.copresencePrivate.sendInitialized(false);
+ return;
+ }
+
+ console.log('init: creating encoder!');
+ whisperEncoder = new WhisperEncoder(audioParams.play, whispernetNacl);
+ whisperEncoder.setAudioDataCallback(chrome.copresencePrivate.sendSamples);
+
+ console.log('init: creating decoder!');
+ whisperDecoder = new WhisperDecoder(audioParams.record, whispernetNacl);
+ whisperDecoder.setReceiveCallback(chrome.copresencePrivate.sendFound);
+ whisperDecoder.onDetectBroadcast(chrome.copresencePrivate.sendDetect);
+
+ chrome.copresencePrivate.sendInitialized(true);
+}
+
+/**
+ * Sends a request to whispernet to encode a token.
+ * @param {string} token Token to encode. This needs to be a base64 string.
+ */
+function encodeTokenRequest(token) {
+ if (whisperEncoder) {
+ whisperEncoder.encode(atob(token), true);
+ } else {
+ console.error('encodeTokenRequest: Whisper not initialized!');
+ }
+}
+
+/**
+ * Sends a request to whispernet to decode samples.
+ * @param {ArrayBuffer} samples Array of samples to decode.
+ */
+function decodeSamplesRequest(samples) {
+ if (whisperDecoder) {
+ whisperDecoder.processSamples(samples);
+ } else {
+ console.error('decodeSamplesRequest: Whisper not initialized!');
+ }
+}
+
+/**
+ * Sends a request to whispernet to detect broadcast.
+ */
+function detectBroadcastRequest() {
+ if (whisperDecoder) {
+ whisperDecoder.detectBroadcast();
+ } else {
+ console.error('detectBroadcastRequest: Whisper not initialized!');
+ }
+}
+
+/**
+ * Initialize our listerners and signal that the extension is loaded.
+ */
+function onWhispernetLoaded() {
+ console.log('init: Nacl ready!');
+
+ // Setup all the listeners for the private API.
+ chrome.copresencePrivate.onInitialize.addListener(initialize);
+ chrome.copresencePrivate.onEncodeTokenRequest.addListener(encodeTokenRequest);
+ chrome.copresencePrivate.onDecodeSamplesRequest.addListener(
+ decodeSamplesRequest);
+ chrome.copresencePrivate.onDetectBroadcastRequest.addListener(
+ detectBroadcastRequest);
+
+ // This first initialized is sent to indicate that the library is loaded.
+ // Every other time, it will be sent only when Chrome wants to reinitialize
+ // the encoder and decoder.
+ chrome.copresencePrivate.sendInitialized(true);
+}
+
+/**
+ * Initialize the whispernet Nacl bridge.
+ */
+function initWhispernet() {
+ console.log('init: Starting Nacl bridge.');
+ // TODO(rkc): Figure out how to embed the .nmf and the .pexe into component
+ // resources without having to rename them to .js.
+ whispernetNacl = new NaclBridge('whispernet_proxy.nmf.png',
+ onWhispernetLoaded);
+}
+
+window.addEventListener('DOMContentLoaded', initWhispernet);
diff --git a/chrome/browser/resources/whispernet_proxy/js/nacl.js b/chrome/browser/resources/whispernet_proxy/js/nacl.js
new file mode 100644
index 0000000..6505728
--- /dev/null
+++ b/chrome/browser/resources/whispernet_proxy/js/nacl.js
@@ -0,0 +1,102 @@
+// Copyright 2014 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.
+
+'use strict';
+
+/**
+ * Constructor for the Nacl bridge to the whispernet wrapper.
+ * @param {string} nmf The relative path to the nmf containing the location of
+ * the whispernet Nacl wrapper.
+ * @param {function()} readyCallback Callback to be called once we've loaded the
+ * whispernet wrapper.
+ */
+function NaclBridge(nmf, readyCallback) {
+ this.readyCallback_ = readyCallback;
+ this.callbacks_ = [];
+ this.isEnabled_ = false;
+ this.naclId_ = this.loadNacl_(nmf);
+}
+
+/**
+ * Method to send generic byte data to the whispernet wrapper.
+ * @param {string} data Raw data to send to the whispernet wrapper.
+ */
+NaclBridge.prototype.send = function(data) {
+ if (this.isEnabled_) {
+ this.embed_.postMessage(data);
+ } else {
+ console.error('Whisper Nacl Bridge not initialized!');
+ }
+};
+
+/**
+ * Method to add a listener to Nacl messages received by this bridge.
+ * @param {function(Event)} messageCallback Callback to receive the messsage.
+ */
+NaclBridge.prototype.addListener = function(messageCallback) {
+ this.callbacks_.push(messageCallback);
+};
+
+/**
+ * Method that receives Nacl messages and forwards them to registered
+ * callbacks.
+ * @param {Event} e Event from the whispernet wrapper.
+ * @private
+ */
+NaclBridge.prototype.onMessage_ = function(e) {
+ if (this.isEnabled_) {
+ this.callbacks_.forEach(function(callback) {
+ callback(e);
+ });
+ }
+};
+
+/**
+ * Injects the <embed> for this nacl manifest URL, generating a unique ID.
+ * @param {string} manifestUrl Url to the nacl manifest to load.
+ * @return {number} generated ID.
+ * @private
+ */
+NaclBridge.prototype.loadNacl_ = function(manifestUrl) {
+ var id = 'nacl-' + Math.floor(Math.random() * 10000);
+ this.embed_ = document.createElement('embed');
+ this.embed_.name = 'nacl_module';
+ this.embed_.width = 1;
+ this.embed_.height = 1;
+ this.embed_.src = manifestUrl;
+ this.embed_.id = id;
+ this.embed_.type = 'application/x-pnacl';
+
+ // Wait for the element to load and callback.
+ this.embed_.addEventListener('load', this.onNaclReady_.bind(this));
+ this.embed_.addEventListener('error', this.onNaclError_.bind(this));
+
+ // Inject the embed string into the page.
+ document.body.appendChild(this.embed_);
+
+ // Listen for messages from the NaCl module.
+ window.addEventListener('message', this.onMessage_.bind(this), true);
+ return id;
+};
+
+/**
+ * Callback that is called when the Whispernet wrapper is loaded and forward
+ * that status to the callback registered with us in the constructor.
+ * @private
+ */
+NaclBridge.prototype.onNaclReady_ = function() {
+ if (this.readyCallback_)
+ this.readyCallback_();
+ this.isEnabled_ = true;
+};
+
+/**
+ * Callback that handles Nacl errors.
+ * @param {string} msg Error string.
+ * @private
+ */
+NaclBridge.prototype.onNaclError_ = function(msg) {
+ // TODO(rkc): Handle error from NaCl better.
+ console.error('NaCl error', msg);
+};
diff --git a/chrome/browser/resources/whispernet_proxy/js/wrapper.js b/chrome/browser/resources/whispernet_proxy/js/wrapper.js
new file mode 100644
index 0000000..3613fd9
--- /dev/null
+++ b/chrome/browser/resources/whispernet_proxy/js/wrapper.js
@@ -0,0 +1,224 @@
+// Copyright 2014 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.
+
+'use strict';
+
+/**
+ * Function to convert an array of bytes to a base64 string
+ * TODO(rkc): Change this to use a Uint8array instead of a string.
+ * @param {string} bytes String containing the bytes we want to convert.
+ * @return {string} String containing the base64 representation.
+ */
+function bytesToBase64(bytes) {
+ var bstr = '';
+ for (var i = 0; i < bytes.length; ++i)
+ bstr += String.fromCharCode(bytes[i]);
+ return btoa(bstr);
+}
+
+/**
+ * Function to convert a string to an array of bytes.
+ * @param {string} str String to convert.
+ * @return {Array} Array containing the string.
+ */
+function stringToArray(str) {
+ var buffer = [];
+ for (var i = 0; i < str.length; ++i)
+ buffer[i] = str.charCodeAt(i);
+ return buffer;
+}
+
+/**
+ * Creates a whispernet encoder.
+ * @constructor
+ * @param {Object} params Dictionary of parameters used to initialize the
+ * whispernet encoder.
+ * @param {Object} whisperNacl The NaclBridge object to use to communicate with
+ * the whispernet wrapper.
+ */
+function WhisperEncoder(params, whisperNacl) {
+ params = params || {};
+ this.repetitions_ = params.repetitions || 3;
+
+ this.whisperNacl_ = whisperNacl;
+ this.whisperNacl_.addListener(this.onNaclMessage_.bind(this));
+
+ var symbolCoder = {};
+ symbolCoder.sample_rate = params.sampleRate || 48000.0;
+ symbolCoder.upsampling_factor = params.bitsPerSample || 16;
+ symbolCoder.desired_carrier_frequency = params.carrierFrequency || 18500.0;
+ symbolCoder.bits_per_symbol = 4;
+ symbolCoder.min_cycles_per_frame = 4;
+ symbolCoder.baseband_decimation_factor = 4;
+
+ var msg = {
+ type: 'initialize_encoder',
+ symbol_coder: symbolCoder,
+ encoder_params: {
+ bytes_per_token: 6,
+ include_parity_symbol: true,
+ single_sideband: true
+ }
+ };
+ this.whisperNacl_.send(JSON.stringify(msg));
+}
+
+/**
+ * Method to encode a token.
+ * @param {string} token Token to encode.
+ * @param {boolean} raw Whether we should return the encoded samples in raw
+ * format or as a Wave file.
+ */
+WhisperEncoder.prototype.encode = function(token, raw) {
+ var msg = {
+ type: 'encode_token',
+ // Trying to send the token in binary form to Nacl doesn't work correctly.
+ // We end up with the correct string + a bunch of extra characters. This is
+ // true of returning a binary string too; hence we communicate back and
+ // forth by converting the bytes into an array of integers.
+ token: stringToArray(token),
+ repetitions: this.repetitions_,
+ return_raw_samples: raw
+ };
+ this.whisperNacl_.send(JSON.stringify(msg));
+};
+
+/**
+ * Method to set the callback for encoded audio data received from the encoder
+ * when we finish encoding a token.
+ * @param {function(string, ArrayBuffer)} callback Callback which will receive
+ * the audio samples.
+ */
+WhisperEncoder.prototype.setAudioDataCallback = function(callback) {
+ this.audioDataCallback_ = callback;
+};
+
+/**
+ * Method to handle messages from the whispernet NaCl wrapper.
+ * @param {Event} e Event from the whispernet wrapper.
+ * @private
+ */
+WhisperEncoder.prototype.onNaclMessage_ = function(e) {
+ var msg = e.data;
+ if (msg.type == 'encode_token_response') {
+ this.audioDataCallback_(bytesToBase64(msg.token), msg.samples);
+ }
+};
+
+/**
+ * Creates a whispernet decoder.
+ * @constructor
+ * @param {Object} params Dictionary of parameters used to initialize the
+ * whispernet decoder.
+ * @param {Object} whisperNacl The NaclBridge object to use to communicate with
+ * the whispernet wrapper.
+ */
+function WhisperDecoder(params, whisperNacl) {
+ params = params || {};
+
+ this.whisperNacl_ = whisperNacl;
+ this.whisperNacl_.addListener(this.onNaclMessage_.bind(this));
+
+ var msg = {
+ type: 'initialize_decoder',
+ num_channels: params.channels,
+ symbol_coder: {
+ sample_rate: params.sampleRate || 48000.0,
+ upsampling_factor: params.bitsPerSample || 16,
+ desired_carrier_frequency: params.carrierFrequency || 18500.0,
+ bits_per_symbol: 4,
+ min_cycles_per_frame: 4,
+ baseband_decimation_factor: 4
+ },
+ decoder_params: {
+ bytes_per_token: 6,
+ include_parity_symbol: true,
+ max_candidates: 1,
+ broadcaster_stopped_threshold_in_seconds: 10
+ },
+ acquisition_params: {
+ max_buffer_duration_in_seconds: 3
+ }
+ };
+ this.whisperNacl_.send(JSON.stringify(msg));
+}
+
+/**
+ * Method to request the decoder to wipe its internal buffer.
+ */
+WhisperDecoder.prototype.wipeDecoder = function() {
+ var msg = {
+ type: 'wipe_decode_buffer'
+ };
+ this.whisperNacl_.send(JSON.stringify(msg));
+};
+
+/**
+ * Method to request the decoder to detect a broadcast.
+ */
+WhisperDecoder.prototype.detectBroadcast = function() {
+ var msg = {
+ type: 'detect_broadcast'
+ };
+ this.whisperNacl_.send(JSON.stringify(msg));
+};
+
+/**
+ * Method to request the decoder to process samples.
+ * @param {ArrayBuffer} samples Array of samples to process.
+ */
+WhisperDecoder.prototype.processSamples = function(samples) {
+ // For sample processing, the Nacl module doesn't expect any frills in the
+ // message, just send the samples directly.
+ this.whisperNacl_.send(samples);
+};
+
+/**
+ * Method to set the callback for decoded tokens received from the decoder.
+ * @param {function(!Array.string)} callback Callback to receive the list of
+ * decoded tokens.
+ */
+WhisperDecoder.prototype.setReceiveCallback = function(callback) {
+ this.tokenCallback_ = callback;
+};
+
+/**
+ * Method to set the callback for receiving the detect callback status received
+ * from the decoder.
+ * @param {function()} callback Callback to set to receive the detect broadcast
+ * status.
+ */
+WhisperDecoder.prototype.onDetectBroadcast = function(callback) {
+ this.detectBroadcastCallback_ = callback;
+};
+
+/**
+ * Method to handle messages from the whispernet NaCl wrapper.
+ * @param {Event} e Event from the whispernet wrapper.
+ * @private
+ */
+WhisperDecoder.prototype.onNaclMessage_ = function(e) {
+ var msg = e.data;
+ if (msg.type == 'decode_tokens_response') {
+ this.handleCandidates_(JSON.parse(msg.tokens));
+ } else if (msg.type == 'detect_broadcast_response') {
+ this.detectBroadcastCallback_(msg.detected);
+ }
+};
+
+/**
+ * Method to receive tokens from the decoder and process and forward them to the
+ * token callback registered with us.
+ * @param {!Array.string} candidates Array of token candidates.
+ * @private
+ */
+WhisperDecoder.prototype.handleCandidates_ = function(candidates) {
+ if (!this.tokenCallback_ || !candidates || candidates.length == 0)
+ return;
+
+ var returnCandidates = [];
+ for (var i = 0; i < candidates.length; ++i)
+ returnCandidates[i] = bytesToBase64(candidates[i]);
+ this.tokenCallback_(returnCandidates);
+};
diff --git a/chrome/browser/resources/whispernet_proxy/manifest.json b/chrome/browser/resources/whispernet_proxy/manifest.json
new file mode 100644
index 0000000..6b15ef9
--- /dev/null
+++ b/chrome/browser/resources/whispernet_proxy/manifest.json
@@ -0,0 +1,13 @@
+{
+ // chrome-extension://bpfmnplchembfbdgieamdodgaencleal/
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxY7MIAZJdh8y5zmU3kRtxMtOWwHl/Yu+c700YQncON0OaqNLcWD/vrPBsMW/4SkkHIBtJDUmLAzPfXyUtbxYo3Vi3E7xJ028BN0Z+KMhPMvFYnHiS4NlFTRDHvQbWi7P+4bvOvS8RZe1VBgSQMhaJSsa6zzE+UNRPsiWNOaX2O08N3oyxo1Z2SU8Q9eE283NOgY8fVg8TJFVqGHPmbcblpM2A+dk7YFBIT1dFemRxJFW2DdLoCXojrNWXqzYstj/muuefRTgnNmwlCjCeDyK6R6x71GRTUnkVOiBgETgsTfOJRvKQCy65/EENZdX/u4e1LM1PWq0ePJ+xMwEXfuHtQIDAQAB",
+ "name": "Whispernet Proxy",
+ "version": "1.0",
+ "manifest_version": 2,
+ "description": "Proxy whispernet calls.",
+ "permissions": [ "copresencePrivate" ],
+ "background": {
+ "page": "background.html",
+ "persistent": true
+ }
+}
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index ed1ce27..58098be 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -353,6 +353,8 @@
'browser/apps/shortcut_manager.h',
'browser/apps/shortcut_manager_factory.cc',
'browser/apps/shortcut_manager_factory.h',
+ 'browser/copresence/chrome_whispernet_client.cc',
+ 'browser/copresence/chrome_whispernet_client.h',
'browser/extensions/active_script_controller.cc',
'browser/extensions/active_script_controller.h',
'browser/extensions/active_tab_permission_granter.cc',
@@ -483,6 +485,8 @@
'browser/extensions/api/cookies/cookies_api_constants.h',
'browser/extensions/api/cookies/cookies_helpers.cc',
'browser/extensions/api/cookies/cookies_helpers.h',
+ 'browser/extensions/api/copresence_private/copresence_private_api.cc',
+ 'browser/extensions/api/copresence_private/copresence_private_api.h',
'browser/extensions/api/debugger/debugger_api.cc',
'browser/extensions/api/debugger/debugger_api.h',
'browser/extensions/api/debugger/debugger_api_constants.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 37e8e82..3f30b4e 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1026,6 +1026,7 @@
'browser/chromeos/ui/idle_logout_dialog_view_browsertest.cc',
'browser/collected_cookies_browsertest.cc',
'browser/content_settings/content_settings_browsertest.cc',
+ 'browser/copresence/chrome_whispernet_client_browsertest.cc',
'browser/crash_recovery_browsertest.cc',
'browser/custom_handlers/protocol_handler_registry_browsertest.cc',
'browser/devtools/device/adb/adb_client_socket_browsertest.cc',
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json
index 0280fca..cd7b197 100644
--- a/chrome/common/extensions/api/_api_features.json
+++ b/chrome/common/extensions/api/_api_features.json
@@ -230,6 +230,10 @@
"dependencies": ["permission:cookies"],
"contexts": ["blessed_extension"]
},
+ "copresencePrivate": {
+ "dependencies": ["permission:copresencePrivate"],
+ "contexts": ["blessed_extension"]
+ },
"debugger": {
"dependencies": ["permission:debugger"],
"contexts": ["blessed_extension"]
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index a3e8662..b313858 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -313,6 +313,14 @@
"channel": "stable",
"extension_types": ["extension", "legacy_packaged_app"]
},
+ "copresencePrivate": {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "location": "component",
+ "whitelist": [
+ "AFA728615D3A82D4017BDECEE86978543194D198" // Whispernet Proxy
+ ]
+ },
"diagnostics": [
{
"channel": "dev",
diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp
index d995e1a..522aba2 100644
--- a/chrome/common/extensions/api/api.gyp
+++ b/chrome/common/extensions/api/api.gyp
@@ -43,6 +43,7 @@
'context_menus_internal.json',
'context_menus.json',
'cookies.json',
+ 'copresence_private.idl',
'debugger.json',
'desktop_capture.json',
'developer_private.idl',
diff --git a/chrome/common/extensions/api/copresence_private.idl b/chrome/common/extensions/api/copresence_private.idl
new file mode 100644
index 0000000..1f9634a
--- /dev/null
+++ b/chrome/common/extensions/api/copresence_private.idl
@@ -0,0 +1,49 @@
+// Copyright 2014 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.
+
+// Use the <code>chrome.copresencePrivate</code> API to interface with Chrome
+// from the whispernet_proxy extension.
+namespace copresencePrivate {
+
+ dictionary PlayParameters {
+ double sampleRate;
+ long bitsPerSample;
+ double carrierFrequency;
+ long repetitions;
+ };
+
+ dictionary RecordParameters {
+ double sampleRate;
+ long bitsPerSample;
+ long channels;
+ double carrierFrequency;
+ };
+
+ dictionary AudioParameters {
+ PlayParameters play;
+ RecordParameters record;
+ };
+
+ interface Functions {
+ // Send a boolean indicating whether our initialization was successful.
+ static void sendInitialized(boolean success);
+ // Sends an array of found tokens to Chrome.
+ static void sendFound(DOMString[] tokens);
+ // Send an array buffer of samples encoded for the specified token.
+ static void sendSamples(DOMString token, ArrayBuffer samples);
+ // Send a boolean indicating whether we detected a broadcast or not.
+ static void sendDetect(boolean detected);
+ };
+
+ interface Events {
+ // Fired to request initialization of the whisper.net library.
+ static void onInitialize(AudioParameters audioParams);
+ // Fired to request encoding of the given token.
+ static void onEncodeTokenRequest(DOMString base64Token);
+ // Fired when we have new samples to decode.
+ static void onDecodeSamplesRequest(ArrayBuffer audioSamples);
+ // Fired to request a DetectBroadcast.
+ static void onDetectBroadcastRequest();
+ };
+};
diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc
index 64258b8..81a3346 100644
--- a/chrome/common/extensions/permissions/chrome_api_permissions.cc
+++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc
@@ -109,6 +109,7 @@ std::vector<APIPermissionInfo*> ChromeAPIPermissions::GetAllPermissions()
PermissionMessage::kContentSettings},
{APIPermission::kContextMenus, "contextMenus"},
{APIPermission::kCookie, "cookies"},
+ {APIPermission::kCopresencePrivate, "copresencePrivate"},
{APIPermission::kEnterprisePlatformKeys, "enterprise.platformKeys"},
{APIPermission::kFileBrowserHandler, "fileBrowserHandler",
APIPermissionInfo::kFlagCannotBeOptional},
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
index 3c5a3c5..908d597 100644
--- a/chrome/common/extensions/permissions/permission_set_unittest.cc
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -644,6 +644,7 @@ TEST(PermissionsTest, PermissionMessages) {
skip.insert(APIPermission::kBrowsingData);
skip.insert(APIPermission::kCastStreaming);
skip.insert(APIPermission::kContextMenus);
+ skip.insert(APIPermission::kCopresencePrivate);
skip.insert(APIPermission::kDiagnostics);
skip.insert(APIPermission::kDns);
skip.insert(APIPermission::kDownloadsShelf);
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 20691b3..5d11e7f 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -922,6 +922,10 @@ enum HistogramValue {
NOTIFICATIONPROVIDER_GETALLNOTIFIERS,
GCDPRIVATE_GETPREFETCHEDWIFINAMELIST,
GUESTVIEWINTERNAL_SETAUTOSIZE,
+ COPRESENCEPRIVATE_SENDFOUND,
+ COPRESENCEPRIVATE_SENDSAMPLES,
+ COPRESENCEPRIVATE_SENDDETECT,
+ COPRESENCEPRIVATE_SENDINITIALIZED,
// Last entry: Add new entries above and ensure to update
// tools/metrics/histograms/histograms/histograms.xml.
ENUM_BOUNDARY
diff --git a/extensions/common/permissions/api_permission.h b/extensions/common/permissions/api_permission.h
index 2ca8e45..4072c03 100644
--- a/extensions/common/permissions/api_permission.h
+++ b/extensions/common/permissions/api_permission.h
@@ -63,6 +63,7 @@ class APIPermission {
kContentSettings,
kContextMenus,
kCookie,
+ kCopresencePrivate,
kDiagnostics,
kDial,
kDebugger,
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 9d1e5082..03f4570 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -39950,6 +39950,10 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<int value="861" label="NOTIFICATIONPROVIDER_GETALLNOTIFIERS"/>
<int value="862" label="GCDPRIVATE_GETPREFETCHEDWIFINAMELIST"/>
<int value="863" label="GUESTVIEWINTERNAL_SETAUTOSIZE"/>
+ <int value="864" label="COPRESENCEPRIVATE_SENDFOUND"/>
+ <int value="865" label="COPRESENCEPRIVATE_SENDSAMPLES"/>
+ <int value="866" label="COPRESENCEPRIVATE_SENDDETECT"/>
+ <int value="867" label="COPRESENCEPRIVATE_SENDINITIALIZED"/>
</enum>
<enum name="ExtensionInstallCause" type="int">