From bb921dfc13aad262dfff7270a8949b6032c6fb23 Mon Sep 17 00:00:00 2001 From: "adrian.belgun" Date: Wed, 20 Jul 2016 01:17:37 -0700 Subject: [PATCH] ppapi: PPB_VpnProvider: Add a simple NaCl SDK example. Co-Authored-By: Valentin Ilie BUG=506490 Review-Url: https://codereview.chromium.org/1731273002 Cr-Commit-Position: refs/heads/master@{#406516} --- .../src/build_tools/sdk_files.list | 1 + .../src/examples/api/vpn_provider/README | 21 +++ .../src/examples/api/vpn_provider/example.dsc | 25 ++++ .../src/examples/api/vpn_provider/example.js | 134 +++++++++++++++++ .../src/examples/api/vpn_provider/index.html | 33 +++++ .../examples/api/vpn_provider/vpn_provider.cc | 137 ++++++++++++++++++ .../api/vpn_provider/vpn_provider_helper.cc | 128 ++++++++++++++++ .../api/vpn_provider/vpn_provider_helper.h | 52 +++++++ 8 files changed, 531 insertions(+) create mode 100644 native_client_sdk/src/examples/api/vpn_provider/README create mode 100644 native_client_sdk/src/examples/api/vpn_provider/example.dsc create mode 100644 native_client_sdk/src/examples/api/vpn_provider/example.js create mode 100644 native_client_sdk/src/examples/api/vpn_provider/index.html create mode 100644 native_client_sdk/src/examples/api/vpn_provider/vpn_provider.cc create mode 100644 native_client_sdk/src/examples/api/vpn_provider/vpn_provider_helper.cc create mode 100644 native_client_sdk/src/examples/api/vpn_provider/vpn_provider_helper.h diff --git a/native_client_sdk/src/build_tools/sdk_files.list b/native_client_sdk/src/build_tools/sdk_files.list index 030bb6b103ac16..97988e4c57205b 100644 --- a/native_client_sdk/src/build_tools/sdk_files.list +++ b/native_client_sdk/src/build_tools/sdk_files.list @@ -21,6 +21,7 @@ examples/api/var_array_buffer/* examples/api/var_dictionary/* examples/api/video_decode/* examples/api/video_encode/* +examples/api/vpn_provider/* examples/api/websocket/* examples/button_close.png examples/button_close_hover.png diff --git a/native_client_sdk/src/examples/api/vpn_provider/README b/native_client_sdk/src/examples/api/vpn_provider/README new file mode 100644 index 00000000000000..09d8d12f2e8e18 --- /dev/null +++ b/native_client_sdk/src/examples/api/vpn_provider/README @@ -0,0 +1,21 @@ +The VpnProvider example demonstrates how to use the VpnProvider API. + +This NaCl SDK example can only be used as an extension on Chrome OS. + +== Compile == +Run "make" from this folder, then open up chrome://extensions +in the browser select "Pack extension...", browse to examples/api/vpn_provider, +click on "Open", then on "Pack extension" and "OK". Copy the .crx to the +Chromebook. + +== Install == +To "sideload" an app or extension under Chrome OS, open up chrome://extensions +in the browser on the Chromebook, then open the file manager with Alt-Shift-M, +then drag the .crx file onto the extensions page. + +== Test == +After loading the extension to a Chromebook, open the 'VPN Provider' app. +Wait for the connection to be created. From the network configuration menu +select the 'Mock configuration' entry. + +Observe the connection setup flow in the "VPN Provider" app. diff --git a/native_client_sdk/src/examples/api/vpn_provider/example.dsc b/native_client_sdk/src/examples/api/vpn_provider/example.dsc new file mode 100644 index 00000000000000..490c78e2389044 --- /dev/null +++ b/native_client_sdk/src/examples/api/vpn_provider/example.dsc @@ -0,0 +1,25 @@ +{ + 'TARGETS': [ + { + 'NAME' : 'vpn_provider', + 'TYPE' : 'main', + 'SOURCES' : [ + 'vpn_provider.cc', + 'vpn_provider_helper.cc', + 'vpn_provider_helper.h' + ], + 'LIBS': ['ppapi_cpp', 'ppapi'] + } + ], + 'DATA': [ + 'example.js', + 'README', + ], + 'DEST': 'examples/api', + 'NAME': 'vpn_provider', + 'TITLE': 'VPN Provider', + 'GROUP': 'API', + 'PERMISSIONS': [ + 'vpnProvider' + ] +} diff --git a/native_client_sdk/src/examples/api/vpn_provider/example.js b/native_client_sdk/src/examples/api/vpn_provider/example.js new file mode 100644 index 00000000000000..c0a590d2f31cb0 --- /dev/null +++ b/native_client_sdk/src/examples/api/vpn_provider/example.js @@ -0,0 +1,134 @@ +// Copyright 2016 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. + +// VPN Configuration identification. +var configName = 'Mock configuration'; +var configId; + +// Example configuration. +var vpnParams = { + 'address': '127.0.0.1/32', + 'mtu': '1000', + 'exclusionList': ['127.0.0.1/32'], + 'inclusionList': ['0.0.0.0/0'], + 'dnsServers': ['8.8.8.8'], + 'reconnect': 'true' +}; + +// Simple log to HTML +function wlog(message) { + var logEl = document.getElementById('log'); + logEl.innerHTML += message + '
'; // Append to log. + logEl.scrollTop = logEl.scrollHeight; // Scroll log to bottom. +} + +// Create a VPN configuration using the createConfig method. +// A VPN configuration is a persistent entry shown to the user in a native +// Chrome OS UI. The user can select a VPN configuration from a list and +// connect to it or disconnect from it. +function create() { + chrome.vpnProvider.createConfig(configName, function(id) { + configId = id; + wlog('JS: Created configuration with name=\'' + configName + '\'' + + ' and id=\'' + configId + '\''); + }); +} + +// Bind connection to NaCl. +function bind() { + common.naclModule.postMessage({cmd: 'bind', name: configName, id: configId}); +} + +function onSetParameters() { + chrome.vpnProvider.setParameters(vpnParams, function() { + wlog('JS: setParameters set!'); + + // Bind connection to NaCl. + bind(); + }); +} + +function onBindSuccess() { + // Notify the connection state as 'connected'. + chrome.vpnProvider.notifyConnectionStateChanged('connected', function() { + wlog('JS: notifyConnectionStateChanged connected!'); + }); +} + +// VpnProviders handlers. +function onPlatformMessageListener(id, message, error) { + wlog('JS: onPlatformMessage: id=\'' + id + '\' message=\'' + message + + '\' error=\'' + error + '\''); + + if (message == 'connected') { + wlog('JS: onPlatformMessage connected!'); + + // Notify NaCl module to connect to the VPN tunnel. + common.naclModule.postMessage({cmd: 'connected'}); + + } else if (message == 'disconnected') { + wlog('JS: onPlatformMessage disconnected!'); + + // Notify NaCl module to disconnect from the VPN tunnel. + common.naclModule.postMessage({cmd: 'disconnected'}); + } +} + +// This function is called by common.js when a message is received from the +// NaCl module. +function handleMessage(message) { + if (typeof message.data === 'string') { + wlog(message.data); + } else if (message.data['cmd'] == 'setParameters') { + onSetParameters(); + } else if (message.data['cmd'] == 'bindSuccess') { + onBindSuccess(); + } +} + +// setupHandlers VpnProviders handlers. +function setupHandlers() { + // Add listeners to the events onPlatformMessage, onPacketReceived and + // onConfigRemoved. + chrome.vpnProvider.onPlatformMessage.addListener(onPlatformMessageListener); + + chrome.vpnProvider.onPacketReceived.addListener(function(data) { + wlog('JS: onPacketReceived'); + console.log('Unexpected event:vpnProvider.onPacketReceived ' + + 'called from JavaScript.'); + }); + + chrome.vpnProvider.onConfigRemoved.addListener(function(id) { + wlog('JS: onConfigRemoved: id=\'' + id + '\''); + }); + + chrome.vpnProvider.onConfigCreated.addListener(function(id, name, data) { + wlog('JS: onConfigCreated: id=\'' + id + '\' name=\'' + name + '\'' + + 'data=' + JSON.stringify(data)); + }); + + chrome.vpnProvider.onUIEvent.addListener(function(event, id) { + wlog('JS: onUIEvent: event=\'' + event + '\' id=\'' + id + '\''); + }); +} + +// This function is called by common.js when the NaCl module is +// loaded. +function moduleDidLoad() { + // Once we load, hide the plugin. In this example, we don't display anything + // in the plugin, so it is fine to hide it. + common.hideModule(); + + if (chrome.vpnProvider === undefined) { + wlog('JS: moduleDidLoad: chrome.vpnProvider undefined.'); + console.log('JS: moduleDidLoad: chrome.vpnProvider undefined.'); + return; + } + + // Setup VpnProvider handlers. + setupHandlers(); + + // All done, create the connection entry in the VPN UI. + create(); +} diff --git a/native_client_sdk/src/examples/api/vpn_provider/index.html b/native_client_sdk/src/examples/api/vpn_provider/index.html new file mode 100644 index 00000000000000..047369f11c3987 --- /dev/null +++ b/native_client_sdk/src/examples/api/vpn_provider/index.html @@ -0,0 +1,33 @@ + + + + + + + {{title}} + + + + + +

{{title}}

+

Status: NO-STATUS

+

The VpnProvider example demonstrates how to use the VpnProvider API.
+ This NaCl SDK example can only be used as an extension on Chrome OS. +
See the README file for detailed information on build and deploy. +

+

Test

+

After loading the extension to a Chromebook, open the 'VPN Provider' app. +
Wait for the connection to be created.
From the network + configuration menu select the 'Mock configuration' entry.
+ Observe the connection setup flow below. + +

+
+
+ + diff --git a/native_client_sdk/src/examples/api/vpn_provider/vpn_provider.cc b/native_client_sdk/src/examples/api/vpn_provider/vpn_provider.cc new file mode 100644 index 00000000000000..88e5aaa3d70b6d --- /dev/null +++ b/native_client_sdk/src/examples/api/vpn_provider/vpn_provider.cc @@ -0,0 +1,137 @@ +// Copyright 2016 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 +#include +#include + +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" +#include "ppapi/cpp/var_dictionary.h" + +#include "vpn_provider_helper.h" + +class VpnProviderInstance : public pp::Instance { + public: + explicit VpnProviderInstance(PP_Instance instance) + : pp::Instance(instance), vpn_provider_helper_(this) { + vpn_provider_helper_.Init(); + } + + virtual ~VpnProviderInstance() {} + + // Handles messages from Javascript + virtual void HandleMessage(const pp::Var& message) { + // Expecting only dictionary messages from JS. + if (!message.is_dictionary()) { + PostMessage( + "NaCl: VpnProviderInstance::HandleMessage: " + "Unexpected message. Not a dictionary."); + return; + } + + // Message type defined by 'cmd' key. + pp::VarDictionary dict(message); + + std::string command; + if (!GetStringFromMessage(dict, "cmd", &command)) { + return; + } + if (command == "bind") { + std::string name, id; + if (!GetStringFromMessage(dict, "name", &name) || + !GetStringFromMessage(dict, "id", &id)) { + return; + } + PostMessage( + "NaCl: VpnProviderInstance::HandleMessage: " + "Bind request."); + vpn_provider_helper_.Bind(name, id); + return; + } + + if (command == "connected") { + PostMessage( + "NaCl: VpnProviderInstance::HandleMessage: " + "Connect request."); + + /* This is the place where the developer would establing the VPN + * connection. The response would usually contain configuration details + * for the tunnel obtained from the VPN implementation. + * + * Currently just signaling that is was executed succesfuly. + */ + + pp::VarDictionary dict; + dict.Set("cmd", "setParameters"); + PostMessage(dict); + return; + } + + if (command == "disconnected") { + PostMessage( + "NaCl: VpnProviderInstance::HandleMessage: " + "Disconnect request."); + + /* This is the place where the developer would disconnect from the VPN + * connection. + */ + + return; + } + + PostMessage( + "NaCl: VpnProviderInstance::HandleMessage: " + "Unexpected command."); + } + + private: + // Helper function for HandleMessage + bool GetStringFromMessage(const pp::VarDictionary& dict, + const char* key, + std::string* value) { + if (!value) + return false; + + pp::Var val = dict.Get(key); + if (val.is_undefined()) { + std::stringstream ss; + ss << "NaCl: VpnProviderInstance::HandleMessage: Malformed message. No '" + << key << "' key."; + PostMessage(ss.str()); + return false; + } + if (!val.is_string()) { + std::stringstream ss; + ss << "NaCl: VpnProviderInstance::HandleMessage: Malformed message. "; + ss << "Type for key '" << key << "' is not string"; + PostMessage(ss.str()); + return false; + } + + *value = val.AsString(); + return true; + } + + VpnProviderHelper vpn_provider_helper_; +}; + +class VpnProviderModule : public pp::Module { + public: + VpnProviderModule() : pp::Module() {} + virtual ~VpnProviderModule() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new VpnProviderInstance(instance); + } +}; + +namespace pp { + +Module* CreateModule() { + return new VpnProviderModule(); +} + +} // namespace pp diff --git a/native_client_sdk/src/examples/api/vpn_provider/vpn_provider_helper.cc b/native_client_sdk/src/examples/api/vpn_provider/vpn_provider_helper.cc new file mode 100644 index 00000000000000..455d9d0d5141c9 --- /dev/null +++ b/native_client_sdk/src/examples/api/vpn_provider/vpn_provider_helper.cc @@ -0,0 +1,128 @@ +// Copyright 2016 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 "vpn_provider_helper.h" + +#include + +#include "ppapi/cpp/var_dictionary.h" + +VpnProviderHelper::VpnProviderHelper(pp::Instance* instance) + : instance_(instance), + thread_(pp::InstanceHandle(instance)), + vpn_(pp::InstanceHandle(instance)), + factory_(this), + send_packet_pending_(false) { + thread_.Start(); +} + +void VpnProviderHelper::Init() { + thread_.message_loop().PostWork( + factory_.NewCallback(&VpnProviderHelper::InitOnThread)); +} + +void VpnProviderHelper::Bind(const std::string& config, + const std::string& name) { + thread_.message_loop().PostWork( + factory_.NewCallback(&VpnProviderHelper::BindOnThread, config, name)); +} + +void VpnProviderHelper::SendPacket(const pp::Var& data) { + thread_.message_loop().PostWork( + factory_.NewCallback(&VpnProviderHelper::SendPacketOnThread, data)); +} + +void VpnProviderHelper::InitOnThread(int32_t result) { + instance_->PostMessage("NaCl: VpnProviderHelper::InitOnThread()"); + if (!vpn_.IsAvailable()) { + instance_->PostMessage( + "NaCl: VpnProviderHelper::InitOnThread(): " + "VpnProvider interface not available!"); + } + + // Initial Callback registration + vpn_.ReceivePacket(factory_.NewCallbackWithOutput( + &VpnProviderHelper::ReceivePacketCompletionCallback)); +} + +void VpnProviderHelper::BindOnThread(int32_t result, + const std::string& config, + const std::string& name) { + instance_->PostMessage("NaCl: VpnProviderHelper::BindOnThread()"); + + vpn_.Bind(config, name, + factory_.NewCallback(&VpnProviderHelper::BindCompletionCallback)); +} + +void VpnProviderHelper::SendPacketOnThread(int32_t result, + const pp::Var& data) { + if (!send_packet_pending_) { + send_packet_pending_ = true; + vpn_.SendPacket( + data, + factory_.NewCallback(&VpnProviderHelper::SendPacketCompletionCallback)); + } else { + std::stringstream ss; + ss << "NaCl: VpnProviderHelper::SendPacketOnThread: " + << "Queueing Packet"; + instance_->PostMessage(ss.str()); + + send_packet_queue_.push(data); + } +} + +void VpnProviderHelper::BindCompletionCallback(int32_t result) { + std::stringstream ss; + ss << "NaCl: VpnProviderHelper::BindCompletionCallback(" << result << ")"; + instance_->PostMessage(ss.str()); + + pp::VarDictionary dict; + dict.Set("cmd", "bindSuccess"); + instance_->PostMessage(dict); +} + +void VpnProviderHelper::ReceivePacketCompletionCallback(int32_t result, + const pp::Var& packet) { + std::stringstream ss; + ss << "NaCl: VpnProviderHelper::ReceivePacketCompletionCallback(" << result + << ")"; + instance_->PostMessage(ss.str()); + + /* This is the place where the developer would unpack the packet from the + * pp:Var and send it the their implementation. Example code below. + * + * pp::VarDictionary message; + * message.Set("operation", "write"); + * message.Set("payload", packet); + * SendMessageToTun((struct PP_Var*)&message.pp_var()); + */ + + // Re-register callback + if (result == PP_OK) { + vpn_.ReceivePacket(factory_.NewCallbackWithOutput( + &VpnProviderHelper::ReceivePacketCompletionCallback)); + } +} + +void VpnProviderHelper::SendPacketCompletionCallback(int32_t result) { + std::stringstream ss; + ss << "NaCl: VpnProviderHelper::SendPacketCompletionCallback(" << result + << ")"; + instance_->PostMessage(ss.str()); + + // If we have queued packets send them before accepting new ones + if (!send_packet_queue_.empty()) { + std::stringstream ss; + ss << "NaCl: VpnProviderHelper::SendPacketCompletionCallback: " + << "Sending queued packed."; + instance_->PostMessage(ss.str()); + + vpn_.SendPacket( + send_packet_queue_.front(), + factory_.NewCallback(&VpnProviderHelper::SendPacketCompletionCallback)); + send_packet_queue_.pop(); + } else { + send_packet_pending_ = false; + } +} diff --git a/native_client_sdk/src/examples/api/vpn_provider/vpn_provider_helper.h b/native_client_sdk/src/examples/api/vpn_provider/vpn_provider_helper.h new file mode 100644 index 00000000000000..e2e359ea544c70 --- /dev/null +++ b/native_client_sdk/src/examples/api/vpn_provider/vpn_provider_helper.h @@ -0,0 +1,52 @@ +// Copyright 2016 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 VPN_PROVIDER_HELPER_H_ +#define VPN_PROVIDER_HELPER_H_ + +#include + +#include +#include + +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/var.h" +#include "ppapi/cpp/vpn_provider.h" +#include "ppapi/utility/completion_callback_factory.h" +#include "ppapi/utility/threading/simple_thread.h" + +// Helper class that keeps VpnPacket handling in its own thread. +class VpnProviderHelper { + public: + explicit VpnProviderHelper(pp::Instance* instance); + + void Init(); + void Bind(const std::string& config_name, const std::string& config_id); + void SendPacket(const pp::Var& data); + + private: + void InitOnThread(int32_t result); + void BindOnThread(int32_t result, + const std::string& config, + const std::string& name); + void SendPacketOnThread(int32_t result, const pp::Var& data); + + // Completion Callbacks + void BindCompletionCallback(int32_t result); + void ReceivePacketCompletionCallback(int32_t result, const pp::Var& packet); + void SendPacketCompletionCallback(int32_t result); + + pp::Instance* instance_; + pp::SimpleThread thread_; + pp::VpnProvider vpn_; + pp::CompletionCallbackFactory factory_; + + bool send_packet_pending_; + std::queue send_packet_queue_; + + std::string config_name_; + std::string config_id_; +}; + +#endif