summaryrefslogtreecommitdiffstats
path: root/blimp
diff options
context:
space:
mode:
authordtrainor <dtrainor@chromium.org>2016-02-19 08:10:25 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-19 16:11:31 +0000
commit6df6164eda86c8ccdb3a0aad8025d9d2a827ee10 (patch)
treef6d6887f207b63f301e99c94873e74d3f52cdf9d /blimp
parent9352dd94f89103fba52d018f24d657e7884fda5d (diff)
downloadchromium_src-6df6164eda86c8ccdb3a0aad8025d9d2a827ee10.zip
chromium_src-6df6164eda86c8ccdb3a0aad8025d9d2a827ee10.tar.gz
chromium_src-6df6164eda86c8ccdb3a0aad8025d9d2a827ee10.tar.bz2
Add assigner support to Blimp
- Add code to contact the assigner for an engine instance. - Add plumbing to pipe the request from Java to native. - Add plumbing to pipe the response back to Java to provide a UI indication of the success or failure of the request. - Connect the assignment response with the BlimpClientSession. BUG=582668 Review URL: https://codereview.chromium.org/1687393002 Cr-Commit-Position: refs/heads/master@{#376447}
Diffstat (limited to 'blimp')
-rw-r--r--blimp/BUILD.gn1
-rw-r--r--blimp/client/BUILD.gn26
-rw-r--r--blimp/client/DEPS2
-rw-r--r--blimp/client/app/android/blimp_client_session_android.cc32
-rw-r--r--blimp/client/app/android/blimp_client_session_android.h13
-rw-r--r--blimp/client/app/android/blimp_library_loader.cc4
-rw-r--r--blimp/client/app/android/java/src/org/chromium/blimp/BlimpRendererActivity.java29
-rw-r--r--blimp/client/app/android/java/src/org/chromium/blimp/session/BlimpClientSession.java72
-rw-r--r--blimp/client/app/android/java/strings/android_blimp_strings.grd33
-rw-r--r--blimp/client/app/blimp_client_switches.cc11
-rw-r--r--blimp/client/app/blimp_client_switches.h3
-rw-r--r--blimp/client/app/linux/blimp_client_session_linux.cc6
-rw-r--r--blimp/client/app/linux/blimp_client_session_linux.h3
-rw-r--r--blimp/client/app/linux/blimp_main.cc8
-rw-r--r--blimp/client/session/assignment_source.cc307
-rw-r--r--blimp/client/session/assignment_source.h76
-rw-r--r--blimp/client/session/assignment_source_unittest.cc355
-rw-r--r--blimp/client/session/blimp_client_session.cc30
-rw-r--r--blimp/client/session/blimp_client_session.h28
-rw-r--r--blimp/common/protocol_version.h3
-rw-r--r--blimp/docs/running.md6
21 files changed, 944 insertions, 104 deletions
diff --git a/blimp/BUILD.gn b/blimp/BUILD.gn
index f8ee944..64345a0 100644
--- a/blimp/BUILD.gn
+++ b/blimp/BUILD.gn
@@ -50,6 +50,7 @@ group("blimp_tests") {
test("blimp_unittests") {
deps = [
"//blimp/client:app_unit_tests",
+ "//blimp/client:blimp_client_unit_tests",
"//blimp/client:feature_unit_tests",
"//blimp/common:unit_tests",
"//blimp/net:unit_tests",
diff --git a/blimp/client/BUILD.gn b/blimp/client/BUILD.gn
index d629ef8..80a164a 100644
--- a/blimp/client/BUILD.gn
+++ b/blimp/client/BUILD.gn
@@ -44,6 +44,23 @@ source_set("blimp_client") {
]
}
+source_set("blimp_client_unit_tests") {
+ testonly = true
+
+ sources = [
+ "session/assignment_source_unittest.cc",
+ ]
+
+ deps = [
+ ":blimp_client",
+ "//base",
+ "//base/test:run_all_unittests",
+ "//base/test:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
source_set("app_unit_tests") {
testonly = true
@@ -258,7 +275,15 @@ if (is_android) {
]
}
+ java_cpp_enum("blimp_java_enums_srcjar") {
+ sources = [
+ "session/assignment_source.h",
+ ]
+ }
+
android_library("blimp_java") {
+ srcjar_deps = [ ":blimp_java_enums_srcjar" ]
+
deps = [
":blimp_java_resources",
"//base:base_java",
@@ -342,6 +367,7 @@ if (is_android) {
":blimp_java",
":blimp_java_resources",
"//base:base_java",
+ "//net/android:net_java",
google_play_services_resources,
]
apk_name = "Blimp"
diff --git a/blimp/client/DEPS b/blimp/client/DEPS
index 06e3f96..62e4870 100644
--- a/blimp/client/DEPS
+++ b/blimp/client/DEPS
@@ -6,7 +6,7 @@ include_rules = [
"-content",
"+gpu",
"+jni",
- "+net/base",
+ "+net",
"+skia",
"+third_party/libwebp",
"+third_party/skia",
diff --git a/blimp/client/app/android/blimp_client_session_android.cc b/blimp/client/app/android/blimp_client_session_android.cc
index 97b8372..2ab42c5 100644
--- a/blimp/client/app/android/blimp_client_session_android.cc
+++ b/blimp/client/app/android/blimp_client_session_android.cc
@@ -4,6 +4,8 @@
#include "blimp/client/app/android/blimp_client_session_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
#include "base/thread_task_runner_handle.h"
#include "blimp/client/feature/tab_control_feature.h"
#include "blimp/client/session/assignment_source.h"
@@ -16,10 +18,7 @@ const int kDummyTabId = 0;
} // namespace
static jlong Init(JNIEnv* env, const JavaParamRef<jobject>& jobj) {
- scoped_ptr<AssignmentSource> assignment_source = make_scoped_ptr(
- new AssignmentSource(base::ThreadTaskRunnerHandle::Get()));
- return reinterpret_cast<intptr_t>(
- new BlimpClientSessionAndroid(env, jobj, std::move(assignment_source)));
+ return reinterpret_cast<intptr_t>(new BlimpClientSessionAndroid(env, jobj));
}
// static
@@ -37,9 +36,7 @@ BlimpClientSessionAndroid* BlimpClientSessionAndroid::FromJavaObject(
BlimpClientSessionAndroid::BlimpClientSessionAndroid(
JNIEnv* env,
- const base::android::JavaParamRef<jobject>& jobj,
- scoped_ptr<AssignmentSource> assignment_source)
- : BlimpClientSession(std::move(assignment_source)) {
+ const base::android::JavaParamRef<jobject>& jobj) {
java_obj_.Reset(env, jobj);
// Create a single tab's WebContents.
@@ -49,8 +46,15 @@ BlimpClientSessionAndroid::BlimpClientSessionAndroid(
void BlimpClientSessionAndroid::Connect(
JNIEnv* env,
- const base::android::JavaParamRef<jobject>& jobj) {
- BlimpClientSession::Connect();
+ const base::android::JavaParamRef<jobject>& jobj,
+ const base::android::JavaParamRef<jstring>& jclient_auth_token) {
+ std::string client_auth_token;
+ if (jclient_auth_token.obj()) {
+ client_auth_token =
+ base::android::ConvertJavaStringToUTF8(env, jclient_auth_token);
+ }
+
+ BlimpClientSession::Connect(client_auth_token);
}
BlimpClientSessionAndroid::~BlimpClientSessionAndroid() {}
@@ -60,5 +64,15 @@ void BlimpClientSessionAndroid::Destroy(JNIEnv* env,
delete this;
}
+void BlimpClientSessionAndroid::OnAssignmentConnectionAttempted(
+ AssignmentSource::Result result) {
+ // Notify the front end of the assignment result.
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_BlimpClientSession_onAssignmentReceived(env, java_obj_.obj(),
+ static_cast<jint>(result));
+
+ BlimpClientSession::OnAssignmentConnectionAttempted(result);
+}
+
} // namespace client
} // namespace blimp
diff --git a/blimp/client/app/android/blimp_client_session_android.h b/blimp/client/app/android/blimp_client_session_android.h
index 7ba6d9f..226188d 100644
--- a/blimp/client/app/android/blimp_client_session_android.h
+++ b/blimp/client/app/android/blimp_client_session_android.h
@@ -20,14 +20,21 @@ class BlimpClientSessionAndroid : public BlimpClientSession {
static BlimpClientSessionAndroid* FromJavaObject(JNIEnv* env, jobject jobj);
BlimpClientSessionAndroid(JNIEnv* env,
- const base::android::JavaParamRef<jobject>& jobj,
- scoped_ptr<AssignmentSource> assignment_source);
+ const base::android::JavaParamRef<jobject>& jobj);
// Methods called from Java via JNI.
- void Connect(JNIEnv* env, const base::android::JavaParamRef<jobject>& jobj);
+ // |jclient_auth_token| is an OAuth2 access token created by GoogleAuthUtil.
+ // See BlimpClientSession::Connect() for more information.
+ void Connect(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jobj,
+ const base::android::JavaParamRef<jstring>& jclient_auth_token);
void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& jobj);
+ // BlimpClientSession overrides.
+ void OnAssignmentConnectionAttempted(
+ AssignmentSource::Result result) override;
+
private:
~BlimpClientSessionAndroid() override;
diff --git a/blimp/client/app/android/blimp_library_loader.cc b/blimp/client/app/android/blimp_library_loader.cc
index e6a009f..bdb8c73 100644
--- a/blimp/client/app/android/blimp_library_loader.cc
+++ b/blimp/client/app/android/blimp_library_loader.cc
@@ -15,6 +15,7 @@
#include "blimp/client/app/android/blimp_jni_registrar.h"
#include "blimp/client/app/blimp_startup.h"
#include "jni/BlimpLibraryLoader_jni.h"
+#include "net/android/net_jni_registrar.h"
#include "ui/gl/gl_surface.h"
namespace {
@@ -33,6 +34,9 @@ bool RegisterJni(JNIEnv* env) {
if (!base::android::RegisterJni(env))
return false;
+ if (!net::android::RegisterJni(env))
+ return false;
+
if (!blimp::client::RegisterBlimpJni(env))
return false;
diff --git a/blimp/client/app/android/java/src/org/chromium/blimp/BlimpRendererActivity.java b/blimp/client/app/android/java/src/org/chromium/blimp/BlimpRendererActivity.java
index 23d52e6..35f8e19 100644
--- a/blimp/client/app/android/java/src/org/chromium/blimp/BlimpRendererActivity.java
+++ b/blimp/client/app/android/java/src/org/chromium/blimp/BlimpRendererActivity.java
@@ -22,12 +22,16 @@ import org.chromium.ui.widget.Toast;
* The {@link Activity} for rendering the main Blimp client. This loads the Blimp rendering stack
* and displays it.
*/
-public class BlimpRendererActivity extends Activity implements BlimpLibraryLoader.Callback,
- TokenSource.Callback {
-
+public class BlimpRendererActivity extends Activity
+ implements BlimpLibraryLoader.Callback, TokenSource.Callback, BlimpClientSession.Callback {
private static final int ACCOUNT_CHOOSER_INTENT_REQUEST_CODE = 100;
private static final String TAG = "Blimp";
+
+ /** Provides user authentication tokens that can be used to query for engine assignments. This
+ * can potentially query GoogleAuthUtil for an OAuth2 authentication token with userinfo.email
+ * privileges for a chosen Android account. */
private TokenSource mTokenSource;
+
private BlimpView mBlimpView;
private Toolbar mToolbar;
private BlimpClientSession mBlimpClientSession;
@@ -37,6 +41,12 @@ public class BlimpRendererActivity extends Activity implements BlimpLibraryLoade
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ // Build a TokenSource that will internally retry accessing the underlying TokenSourceImpl.
+ // This will exponentially backoff while it tries to get the access token. See
+ // {@link RetryingTokenSource} for more information. The underlying
+ // TokenSourceImpl will attempt to query GoogleAuthUtil, but might fail if there is no
+ // account selected, in which case it will ask this Activity to show an account chooser and
+ // notify it of the selection result.
mTokenSource = new RetryingTokenSource(new TokenSourceImpl(this));
mTokenSource.setCallback(this);
mTokenSource.getToken();
@@ -115,8 +125,7 @@ public class BlimpRendererActivity extends Activity implements BlimpLibraryLoade
setContentView(R.layout.blimp_main);
- mBlimpClientSession = new BlimpClientSession();
- mBlimpClientSession.connect();
+ mBlimpClientSession = new BlimpClientSession(this);
mBlimpView = (BlimpView) findViewById(R.id.renderer);
mBlimpView.initializeRenderer(mBlimpClientSession);
@@ -131,8 +140,7 @@ public class BlimpRendererActivity extends Activity implements BlimpLibraryLoade
// TokenSource.Callback implementation.
@Override
public void onTokenReceived(String token) {
- // TODO(dtrainor): Do something with the token and the assigner!
- Toast.makeText(this, R.string.signin_get_token_succeeded, Toast.LENGTH_SHORT).show();
+ if (mBlimpClientSession != null) mBlimpClientSession.connect(token);
}
@Override
@@ -140,11 +148,16 @@ public class BlimpRendererActivity extends Activity implements BlimpLibraryLoade
// Ignore isTransient here because we're relying on the auto-retry TokenSource.
// TODO(dtrainor): Show a better error dialog/message.
Toast.makeText(this, R.string.signin_get_token_failed, Toast.LENGTH_LONG).show();
- finish();
}
@Override
public void onNeedsAccountToBeSelected(Intent suggestedIntent) {
startActivityForResult(suggestedIntent, ACCOUNT_CHOOSER_INTENT_REQUEST_CODE);
}
+
+ // BlimpClientSession.Callback implementation.
+ @Override
+ public void onAssignmentReceived(int result, int suggestedMessageResourceId) {
+ Toast.makeText(this, suggestedMessageResourceId, Toast.LENGTH_LONG).show();
+ }
}
diff --git a/blimp/client/app/android/java/src/org/chromium/blimp/session/BlimpClientSession.java b/blimp/client/app/android/java/src/org/chromium/blimp/session/BlimpClientSession.java
index b3012d8..567c623 100644
--- a/blimp/client/app/android/java/src/org/chromium/blimp/session/BlimpClientSession.java
+++ b/blimp/client/app/android/java/src/org/chromium/blimp/session/BlimpClientSession.java
@@ -6,6 +6,8 @@ package org.chromium.blimp.session;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
+import org.chromium.blimp.R;
+import org.chromium.blimp.assignment.Result;
/**
* The Java representation of a native BlimpClientSession. This is primarily used to provide access
@@ -14,17 +16,35 @@ import org.chromium.base.annotations.JNINamespace;
*/
@JNINamespace("blimp::client")
public class BlimpClientSession {
+ /**
+ * A callback for when the session needs to notify the UI about the state of the Blimp session.
+ */
+ public interface Callback {
+ /**
+ * Called when an engine assignment has been successful or failed.
+ * @param result The result code of the assignment. See
+ * assignment_source.h for details. Maps to a value in
+ * {@link Result}.
+ * @param suggestedMessageResourceId A suggested resource id for a string to display to the
+ * user if necessary.
+ */
+ void onAssignmentReceived(int result, int suggestedMessageResourceId);
+ }
+
+ private final Callback mCallback;
private long mNativeBlimpClientSessionAndroidPtr;
- public BlimpClientSession() {
+ public BlimpClientSession(Callback callback) {
+ mCallback = callback;
mNativeBlimpClientSessionAndroidPtr = nativeInit();
}
/**
* Retrieves an assignment and uses it to connect to the engine.
+ * @param token A OAuth2 access token for the account requesting access.
*/
- public void connect() {
- nativeConnect(mNativeBlimpClientSessionAndroidPtr);
+ public void connect(String token) {
+ nativeConnect(mNativeBlimpClientSessionAndroidPtr, token);
}
/**
@@ -39,12 +59,56 @@ public class BlimpClientSession {
// Methods that are called by native via JNI.
@CalledByNative
+ private void onAssignmentReceived(int result) {
+ if (mCallback == null) return;
+
+ int resultMessageResourceId = R.string.assignment_failure_unknown;
+ switch (result) {
+ case Result.OK:
+ resultMessageResourceId = R.string.assignment_success;
+ break;
+ case Result.BAD_REQUEST:
+ resultMessageResourceId = R.string.assignment_failure_bad_request;
+ break;
+ case Result.BAD_RESPONSE:
+ resultMessageResourceId = R.string.assignment_failure_bad_response;
+ break;
+ case Result.INVALID_PROTOCOL_VERSION:
+ resultMessageResourceId = R.string.assignment_failure_bad_version;
+ break;
+ case Result.EXPIRED_ACCESS_TOKEN:
+ resultMessageResourceId = R.string.assignment_failure_expired_token;
+ break;
+ case Result.USER_INVALID:
+ resultMessageResourceId = R.string.assignment_failure_user_invalid;
+ break;
+ case Result.OUT_OF_VMS:
+ resultMessageResourceId = R.string.assignment_failure_out_of_vms;
+ break;
+ case Result.SERVER_ERROR:
+ resultMessageResourceId = R.string.assignment_failure_server_error;
+ break;
+ case Result.SERVER_INTERRUPTED:
+ resultMessageResourceId = R.string.assignment_failure_server_interrupted;
+ break;
+ case Result.NETWORK_FAILURE:
+ resultMessageResourceId = R.string.assignment_failure_network;
+ break;
+ case Result.UNKNOWN:
+ default:
+ resultMessageResourceId = R.string.assignment_failure_unknown;
+ break;
+ }
+ mCallback.onAssignmentReceived(result, resultMessageResourceId);
+ }
+
+ @CalledByNative
private long getNativePtr() {
assert mNativeBlimpClientSessionAndroidPtr != 0;
return mNativeBlimpClientSessionAndroidPtr;
}
private native long nativeInit();
- private native void nativeConnect(long nativeBlimpClientSessionAndroid);
+ private native void nativeConnect(long nativeBlimpClientSessionAndroid, String token);
private native void nativeDestroy(long nativeBlimpClientSessionAndroid);
}
diff --git a/blimp/client/app/android/java/strings/android_blimp_strings.grd b/blimp/client/app/android/java/strings/android_blimp_strings.grd
index c245fe4..6b4a42c 100644
--- a/blimp/client/app/android/java/strings/android_blimp_strings.grd
+++ b/blimp/client/app/android/java/strings/android_blimp_strings.grd
@@ -103,6 +103,39 @@
<message name="IDS_SIGNIN_CHOOSER_DESCRIPTION" desc="Message shown with the Android signin chooser screen.">
Please choose an account to use with Blimp.
</message>
+ <message name="IDS_ASSIGNMENT_FAILURE_UNKNOWN" desc="Message for when getting an assignment has failed due to an unknown issue.">
+ Assignment failed. Unknown issue.
+ </message>
+ <message name="IDS_ASSIGNMENT_FAILURE_BAD_REQUEST" desc="Message for when getting an assignment has failed due to a bad request.">
+ Assignment failed. 400 (BAD_REQUEST) error.
+ </message>
+ <message name="IDS_ASSIGNMENT_FAILURE_BAD_RESPONSE" desc="Message for when getting an assignment has failed due to a bad response.">
+ Assignment failed. Invalid response.
+ </message>
+ <message name="IDS_ASSIGNMENT_FAILURE_BAD_VERSION" desc="Message for when getting an assignment has failed due to an out of date client.">
+ Assignment failed. No matching engine version.
+ </message>
+ <message name="IDS_ASSIGNMENT_FAILURE_EXPIRED_TOKEN" desc="Message for when getting an assignment has failed due to an expired user token.">
+ Assignment failed. Expired user access token.
+ </message>
+ <message name="IDS_ASSIGNMENT_FAILURE_USER_INVALID" desc="Message for when getting an assignment has failed due to an invalid user.">
+ Assignment failed. Account does not have access.
+ </message>
+ <message name="IDS_ASSIGNMENT_FAILURE_OUT_OF_VMS" desc="Message for when getting an assignment has failed due to a shortage of engines.">
+ Assignment failed. No engine available.
+ </message>
+ <message name="IDS_ASSIGNMENT_FAILURE_SERVER_ERROR" desc="Message for when getting an assignment has failed due to a server error.">
+ Assignment failed. Server error.
+ </message>
+ <message name="IDS_ASSIGNMENT_FAILURE_SERVER_INTERRUPTED" desc="Message for when getting an assignment has failed due to another request.">
+ Assignment failed. Second request sent before first request responded.
+ </message>
+ <message name="IDS_ASSIGNMENT_FAILURE_NETWORK" desc="Message for when getting an assignment has failed due to a general network issue.">
+ Assignment failed. Network request failed.
+ </message>
+ <message name="IDS_ASSIGNMENT_SUCCESS" desc="Message for when getting an assignment succeeds.">
+ Assignment succeeded.
+ </message>
</messages>
</release>
</grit> \ No newline at end of file
diff --git a/blimp/client/app/blimp_client_switches.cc b/blimp/client/app/blimp_client_switches.cc
index 445ebe9..cfcf2a0 100644
--- a/blimp/client/app/blimp_client_switches.cc
+++ b/blimp/client/app/blimp_client_switches.cc
@@ -7,14 +7,11 @@
namespace blimp {
namespace switches {
-// Specifies the blimplet IP-address to connect to, e.g.:
-// --blimplet-host="127.0.0.1".
+// Specifies the blimplet scheme, IP-address and port to connect to, e.g.:
+// --blimplet-host="tcp:127.0.0.1:25467". Valid schemes are "ssl",
+// "tcp", and "quic".
// TODO(nyquist): Add support for DNS-lookup. See http://crbug.com/576857.
-const char kBlimpletHost[] = "blimplet-host";
-
-// Specifies the blimplet port to connect to, e.g.:
-// --blimplet-tcp-port=25467.
-const char kBlimpletTCPPort[] = "blimplet-tcp-port";
+const char kBlimpletEndpoint[] = "blimplet-endpoint";
} // namespace switches
} // namespace blimp
diff --git a/blimp/client/app/blimp_client_switches.h b/blimp/client/app/blimp_client_switches.h
index 88167f1..19b91dd 100644
--- a/blimp/client/app/blimp_client_switches.h
+++ b/blimp/client/app/blimp_client_switches.h
@@ -10,8 +10,7 @@
namespace blimp {
namespace switches {
-extern const char kBlimpletHost[];
-extern const char kBlimpletTCPPort[];
+extern const char kBlimpletEndpoint[];
} // namespace switches
} // namespace blimp
diff --git a/blimp/client/app/linux/blimp_client_session_linux.cc b/blimp/client/app/linux/blimp_client_session_linux.cc
index d4491be8..84317ca 100644
--- a/blimp/client/app/linux/blimp_client_session_linux.cc
+++ b/blimp/client/app/linux/blimp_client_session_linux.cc
@@ -59,10 +59,8 @@ void FakeNavigationFeatureDelegate::OnLoadingChanged(int tab_id, bool loading) {
} // namespace
-BlimpClientSessionLinux::BlimpClientSessionLinux(
- scoped_ptr<AssignmentSource> assignment_source)
- : BlimpClientSession(std::move(assignment_source)),
- event_source_(ui::PlatformEventSource::CreateDefault()),
+BlimpClientSessionLinux::BlimpClientSessionLinux()
+ : event_source_(ui::PlatformEventSource::CreateDefault()),
navigation_feature_delegate_(new FakeNavigationFeatureDelegate) {
blimp_display_manager_.reset(new BlimpDisplayManager(gfx::Size(800, 600),
this,
diff --git a/blimp/client/app/linux/blimp_client_session_linux.h b/blimp/client/app/linux/blimp_client_session_linux.h
index 119feba..1a60b0d 100644
--- a/blimp/client/app/linux/blimp_client_session_linux.h
+++ b/blimp/client/app/linux/blimp_client_session_linux.h
@@ -20,8 +20,7 @@ namespace client {
class BlimpClientSessionLinux : public BlimpClientSession,
public BlimpDisplayManagerDelegate {
public:
- explicit BlimpClientSessionLinux(
- scoped_ptr<AssignmentSource> assignment_source);
+ BlimpClientSessionLinux();
~BlimpClientSessionLinux() override;
// BlimpDisplayManagerDelegate implementation.
diff --git a/blimp/client/app/linux/blimp_main.cc b/blimp/client/app/linux/blimp_main.cc
index 332a793..3eedffe 100644
--- a/blimp/client/app/linux/blimp_main.cc
+++ b/blimp/client/app/linux/blimp_main.cc
@@ -18,6 +18,7 @@
#include "blimp/client/session/assignment_source.h"
namespace {
+const char kDummyLoginToken[] = "";
const char kDefaultUrl[] = "https://www.google.com";
const int kDummyTabId = 0;
}
@@ -31,12 +32,9 @@ int main(int argc, const char**argv) {
blimp::client::InitializeLogging();
blimp::client::InitializeMainMessageLoop();
- scoped_ptr<blimp::client::AssignmentSource> assignment_source =
- make_scoped_ptr(new blimp::client::AssignmentSource(
- base::ThreadTaskRunnerHandle::Get()));
- blimp::client::BlimpClientSessionLinux session(std::move(assignment_source));
+ blimp::client::BlimpClientSessionLinux session;
session.GetTabControlFeature()->CreateTab(kDummyTabId);
- session.Connect();
+ session.Connect(kDummyLoginToken);
// If there is a non-switch argument to the command line, load that url.
base::CommandLine::StringVector args =
diff --git a/blimp/client/session/assignment_source.cc b/blimp/client/session/assignment_source.cc
index a83c66f..242d783 100644
--- a/blimp/client/session/assignment_source.cc
+++ b/blimp/client/session/assignment_source.cc
@@ -5,71 +5,308 @@
#include "blimp/client/session/assignment_source.h"
#include "base/bind.h"
+#include "base/callback_helpers.h"
#include "base/command_line.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
#include "blimp/client/app/blimp_client_switches.h"
+#include "blimp/common/protocol_version.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/proxy/proxy_config_service.h"
+#include "net/proxy/proxy_service.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_builder.h"
+#include "net/url_request/url_request_context_getter.h"
namespace blimp {
+namespace client {
+
namespace {
-// TODO(kmarshall): Take values from configuration data.
-const char kDummyClientToken[] = "MyVoiceIsMyPassport";
-const std::string kDefaultBlimpletIPAddress = "127.0.0.1";
-const uint16_t kDefaultBlimpletTCPPort = 25467;
+// Assignment request JSON keys.
+const char kProtocolVersionKey[] = "protocol_version";
+
+// Assignment response JSON keys.
+const char kClientTokenKey[] = "clientToken";
+const char kHostKey[] = "host";
+const char kPortKey[] = "port";
+const char kCertificateFingerprintKey[] = "certificateFingerprint";
+const char kCertificateKey[] = "certificate";
+
+// URL scheme constants for custom assignments. See the '--blimplet-endpoint'
+// documentation in blimp_client_switches.cc for details.
+const char kCustomSSLScheme[] = "ssl";
+const char kCustomTCPScheme[] = "tcp";
+const char kCustomQUICScheme[] = "quic";
+
+Assignment GetCustomBlimpletAssignment() {
+ GURL url(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kBlimpletEndpoint));
-net::IPAddress GetBlimpletIPAddress() {
std::string host;
- if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kBlimpletHost)) {
- host = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- switches::kBlimpletHost);
- } else {
- host = kDefaultBlimpletIPAddress;
+ int port;
+ if (url.is_empty() || !url.is_valid() || !url.has_scheme() ||
+ !net::ParseHostAndPort(url.path(), &host, &port)) {
+ return Assignment();
}
+
net::IPAddress ip_address;
- if (!ip_address.AssignFromIPLiteral(host))
+ if (!ip_address.AssignFromIPLiteral(host)) {
CHECK(false) << "Invalid BlimpletAssignment host " << host;
- return ip_address;
-}
+ }
-uint16_t GetBlimpletTCPPort() {
- if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kBlimpletTCPPort)) {
- std::string port_str =
- base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- switches::kBlimpletTCPPort);
- uint port_64t;
- if (!base::StringToUint(port_str, &port_64t) ||
- !base::IsValueInRangeForNumericType<uint16_t>(port_64t)) {
- CHECK(false) << "Invalid BlimpletAssignment port " << port_str;
+ if (!base::IsValueInRangeForNumericType<uint16_t>(port)) {
+ CHECK(false) << "Invalid BlimpletAssignment port " << port;
+ }
+
+ Assignment::TransportProtocol protocol =
+ Assignment::TransportProtocol::UNKNOWN;
+ if (url.has_scheme()) {
+ if (url.SchemeIs(kCustomSSLScheme)) {
+ protocol = Assignment::TransportProtocol::SSL;
+ } else if (url.SchemeIs(kCustomTCPScheme)) {
+ protocol = Assignment::TransportProtocol::TCP;
+ } else if (url.SchemeIs(kCustomQUICScheme)) {
+ protocol = Assignment::TransportProtocol::QUIC;
+ } else {
+ CHECK(false) << "Invalid BlimpletAssignment scheme " << url.scheme();
}
- return base::checked_cast<uint16_t>(port_64t);
- } else {
- return kDefaultBlimpletTCPPort;
}
+
+ Assignment assignment;
+ assignment.transport_protocol = protocol;
+ assignment.ip_endpoint = net::IPEndPoint(ip_address, port);
+ assignment.client_token = kDummyClientToken;
+ return assignment;
+}
+
+GURL GetBlimpAssignerURL() {
+ // TODO(dtrainor): Add a way to specify another assigner.
+ return GURL(kDefaultAssignerURL);
}
+class SimpleURLRequestContextGetter : public net::URLRequestContextGetter {
+ public:
+ SimpleURLRequestContextGetter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_loop_task_runner)
+ : io_loop_task_runner_(io_loop_task_runner),
+ proxy_config_service_(net::ProxyService::CreateSystemProxyConfigService(
+ io_loop_task_runner_,
+ io_loop_task_runner_)) {}
+
+ // net::URLRequestContextGetter implementation.
+ net::URLRequestContext* GetURLRequestContext() override {
+ if (!url_request_context_) {
+ net::URLRequestContextBuilder builder;
+ builder.set_proxy_config_service(std::move(proxy_config_service_));
+ builder.DisableHttpCache();
+ url_request_context_ = builder.Build();
+ }
+
+ return url_request_context_.get();
+ }
+
+ scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner()
+ const override {
+ return io_loop_task_runner_;
+ }
+
+ private:
+ ~SimpleURLRequestContextGetter() override {}
+
+ scoped_refptr<base::SingleThreadTaskRunner> io_loop_task_runner_;
+ scoped_ptr<net::URLRequestContext> url_request_context_;
+
+ // Temporary storage for the ProxyConfigService, which needs to be created on
+ // the main thread but cleared on the IO thread. This will be built in the
+ // constructor and cleared on the IO thread. Due to the usage of this class
+ // this is safe.
+ scoped_ptr<net::ProxyConfigService> proxy_config_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleURLRequestContextGetter);
+};
+
} // namespace
-namespace client {
+Assignment::Assignment() : transport_protocol(TransportProtocol::UNKNOWN) {}
+
+Assignment::~Assignment() {}
+
+bool Assignment::is_null() const {
+ return ip_endpoint.address().empty() || ip_endpoint.port() == 0 ||
+ transport_protocol == TransportProtocol::UNKNOWN;
+}
AssignmentSource::AssignmentSource(
- const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner)
- : main_task_runner_(main_task_runner) {}
+ const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
+ : main_task_runner_(main_task_runner),
+ url_request_context_(new SimpleURLRequestContextGetter(io_task_runner)) {}
AssignmentSource::~AssignmentSource() {}
-void AssignmentSource::GetAssignment(const AssignmentCallback& callback) {
+void AssignmentSource::GetAssignment(const std::string& client_auth_token,
+ const AssignmentCallback& callback) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ // Cancel any outstanding callback.
+ if (!callback_.is_null()) {
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_SERVER_INTERRUPTED, Assignment());
+ }
+ callback_ = AssignmentCallback(callback);
+
+ Assignment assignment = GetCustomBlimpletAssignment();
+ if (!assignment.is_null()) {
+ // Post the result so that the behavior of this function is consistent.
+ main_task_runner_->PostTask(
+ FROM_HERE, base::Bind(base::ResetAndReturn(&callback_),
+ AssignmentSource::Result::RESULT_OK, assignment));
+ return;
+ }
+
+ // Call out to the network for a real assignment. Build the network request
+ // to hit the assigner.
+ url_fetcher_ = net::URLFetcher::Create(GetBlimpAssignerURL(),
+ net::URLFetcher::POST, this);
+ url_fetcher_->SetRequestContext(url_request_context_.get());
+ url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES);
+ url_fetcher_->AddExtraRequestHeader("Authorization: Bearer " +
+ client_auth_token);
+
+ // Write the JSON for the request data.
+ base::DictionaryValue dictionary;
+ dictionary.SetString(kProtocolVersionKey, blimp::kEngineVersion);
+ std::string json;
+ base::JSONWriter::Write(dictionary, &json);
+ url_fetcher_->SetUploadData("application/json", json);
+
+ url_fetcher_->Start();
+}
+
+void AssignmentSource::OnURLFetchComplete(const net::URLFetcher* source) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ DCHECK(!callback_.is_null());
+ DCHECK_EQ(url_fetcher_.get(), source);
+
+ if (!source->GetStatus().is_success()) {
+ DVLOG(1) << "Assignment request failed due to network error: "
+ << net::ErrorToString(source->GetStatus().error());
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_NETWORK_FAILURE, Assignment());
+ return;
+ }
+
+ switch (source->GetResponseCode()) {
+ case net::HTTP_OK:
+ ParseAssignerResponse();
+ break;
+ case net::HTTP_BAD_REQUEST:
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_BAD_REQUEST, Assignment());
+ break;
+ case net::HTTP_UNAUTHORIZED:
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_EXPIRED_ACCESS_TOKEN,
+ Assignment());
+ break;
+ case net::HTTP_FORBIDDEN:
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_USER_INVALID, Assignment());
+ break;
+ case 429: // Too Many Requests
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_OUT_OF_VMS, Assignment());
+ break;
+ case net::HTTP_INTERNAL_SERVER_ERROR:
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_SERVER_ERROR, Assignment());
+ break;
+ default:
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_BAD_RESPONSE, Assignment());
+ break;
+ }
+}
+
+void AssignmentSource::ParseAssignerResponse() {
+ DCHECK(url_fetcher_.get());
+ DCHECK(url_fetcher_->GetStatus().is_success());
+ DCHECK_EQ(net::HTTP_OK, url_fetcher_->GetResponseCode());
+
+ // Grab the response from the assigner request.
+ std::string response;
+ if (!url_fetcher_->GetResponseAsString(&response)) {
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_BAD_RESPONSE, Assignment());
+ return;
+ }
+
+ // Attempt to interpret the response as JSON and treat it as a dictionary.
+ scoped_ptr<base::Value> json = base::JSONReader::Read(response);
+ if (!json) {
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_BAD_RESPONSE, Assignment());
+ return;
+ }
+
+ const base::DictionaryValue* dict;
+ if (!json->GetAsDictionary(&dict)) {
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_BAD_RESPONSE, Assignment());
+ return;
+ }
+
+ // Validate that all the expected fields are present.
+ std::string client_token;
+ std::string host;
+ int port;
+ std::string cert_fingerprint;
+ std::string cert;
+ if (!(dict->GetString(kClientTokenKey, &client_token) &&
+ dict->GetString(kHostKey, &host) && dict->GetInteger(kPortKey, &port) &&
+ dict->GetString(kCertificateFingerprintKey, &cert_fingerprint) &&
+ dict->GetString(kCertificateKey, &cert))) {
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_BAD_RESPONSE, Assignment());
+ return;
+ }
+
+ net::IPAddress ip_address;
+ if (!ip_address.AssignFromIPLiteral(host)) {
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_BAD_RESPONSE, Assignment());
+ return;
+ }
+
+ if (!base::IsValueInRangeForNumericType<uint16_t>(port)) {
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_BAD_RESPONSE, Assignment());
+ return;
+ }
+
Assignment assignment;
- assignment.ip_endpoint =
- net::IPEndPoint(GetBlimpletIPAddress(), GetBlimpletTCPPort());
- assignment.client_token = kDummyClientToken;
- main_task_runner_->PostTask(FROM_HERE, base::Bind(callback, assignment));
+ // The assigner assumes SSL-only and all engines it assigns only communicate
+ // over SSL.
+ assignment.transport_protocol = Assignment::TransportProtocol::SSL;
+ assignment.ip_endpoint = net::IPEndPoint(ip_address, port);
+ assignment.client_token = client_token;
+ assignment.certificate = cert;
+ assignment.certificate_fingerprint = cert_fingerprint;
+
+ base::ResetAndReturn(&callback_)
+ .Run(AssignmentSource::Result::RESULT_OK, assignment);
}
} // namespace client
diff --git a/blimp/client/session/assignment_source.h b/blimp/client/session/assignment_source.h
index 9891d4a..dabe72c 100644
--- a/blimp/client/session/assignment_source.h
+++ b/blimp/client/session/assignment_source.h
@@ -10,41 +10,105 @@
#include "base/callback.h"
#include "blimp/client/blimp_client_export.h"
#include "net/base/ip_endpoint.h"
+#include "net/url_request/url_fetcher_delegate.h"
namespace base {
class SingleThreadTaskRunner;
}
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+}
+
namespace blimp {
namespace client {
+// TODO(kmarshall): Take values from configuration data.
+const char kDummyClientToken[] = "MyVoiceIsMyPassport";
+
+// Potential assigner URLs.
+const char kDefaultAssignerURL[] =
+ "https://blimp-pa.googleapis.com/v1/assignment";
+
// An Assignment contains the configuration data needed for a client
// to connect to the engine.
struct BLIMP_CLIENT_EXPORT Assignment {
+ enum TransportProtocol {
+ UNKNOWN = 0,
+ SSL = 1,
+ TCP = 2,
+ QUIC = 3,
+ };
+
+ Assignment();
+ ~Assignment();
+
+ TransportProtocol transport_protocol;
net::IPEndPoint ip_endpoint;
std::string client_token;
+ std::string certificate;
+ std::string certificate_fingerprint;
+
+ // Returns true if the net::IPEndPoint has an unspecified IP, port, or
+ // transport protocol.
+ bool is_null() const;
};
// AssignmentSource provides functionality to find out how a client should
// connect to an engine.
-class BLIMP_CLIENT_EXPORT AssignmentSource {
+class BLIMP_CLIENT_EXPORT AssignmentSource : public net::URLFetcherDelegate {
public:
- typedef const base::Callback<void(const Assignment&)> AssignmentCallback;
+ // A Java counterpart will be generated for this enum.
+ // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.blimp.assignment
+ enum Result {
+ RESULT_UNKNOWN = 0,
+ RESULT_OK = 1,
+ RESULT_BAD_REQUEST = 2,
+ RESULT_BAD_RESPONSE = 3,
+ RESULT_INVALID_PROTOCOL_VERSION = 4,
+ RESULT_EXPIRED_ACCESS_TOKEN = 5,
+ RESULT_USER_INVALID = 6,
+ RESULT_OUT_OF_VMS = 7,
+ RESULT_SERVER_ERROR = 8,
+ RESULT_SERVER_INTERRUPTED = 9,
+ RESULT_NETWORK_FAILURE = 10
+ };
+
+ typedef base::Callback<void(AssignmentSource::Result, const Assignment&)>
+ AssignmentCallback;
// The |main_task_runner| should be the task runner for the UI thread because
// this will in some cases be used to trigger user interaction on the UI
// thread.
AssignmentSource(
- const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner);
- virtual ~AssignmentSource();
+ const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner);
+ ~AssignmentSource() override;
// Retrieves a valid assignment for the client and posts the result to the
- // given callback.
- void GetAssignment(const AssignmentCallback& callback);
+ // given callback. |client_auth_token| is the OAuth2 access token to send to
+ // the assigner when requesting an assignment. If this is called before a
+ // previous call has completed, the old callback will be called with
+ // RESULT_SERVER_INTERRUPTED and no Assignment.
+ void GetAssignment(const std::string& client_auth_token,
+ const AssignmentCallback& callback);
+
+ // net::URLFetcherDelegate implementation:
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
private:
+ void ParseAssignerResponse();
+
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_;
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+
+ // This callback is set during a call to GetAssignment() and is cleared after
+ // the request has completed (whether it be a success or failure).
+ AssignmentCallback callback_;
+
DISALLOW_COPY_AND_ASSIGN(AssignmentSource);
};
diff --git a/blimp/client/session/assignment_source_unittest.cc b/blimp/client/session/assignment_source_unittest.cc
new file mode 100644
index 0000000..d893ae3
--- /dev/null
+++ b/blimp/client/session/assignment_source_unittest.cc
@@ -0,0 +1,355 @@
+// 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 "blimp/client/session/assignment_source.h"
+
+#include "base/command_line.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "blimp/client/app/blimp_client_switches.h"
+#include "blimp/common/protocol_version.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::InSequence;
+
+namespace blimp {
+namespace client {
+namespace {
+
+MATCHER_P(AssignmentEquals, assignment, "") {
+ return arg.transport_protocol == assignment.transport_protocol &&
+ arg.ip_endpoint == assignment.ip_endpoint &&
+ arg.client_token == assignment.client_token &&
+ arg.certificate == assignment.certificate &&
+ arg.certificate_fingerprint == assignment.certificate_fingerprint;
+}
+
+net::IPEndPoint BuildIPEndPoint(const std::string& ip, int port) {
+ net::IPAddress ip_address;
+ EXPECT_TRUE(ip_address.AssignFromIPLiteral(ip));
+
+ return net::IPEndPoint(ip_address, port);
+}
+
+Assignment BuildValidAssignment() {
+ Assignment assignment;
+ assignment.transport_protocol = Assignment::TransportProtocol::SSL;
+ assignment.ip_endpoint = BuildIPEndPoint("100.150.200.250", 500);
+ assignment.client_token = "SecretT0kenz";
+ assignment.certificate_fingerprint = "WhaleWhaleWhale";
+ assignment.certificate = "whaaaaaaaaaaaaale";
+ return assignment;
+}
+
+std::string BuildResponseFromAssignment(const Assignment& assignment) {
+ base::DictionaryValue dict;
+ dict.SetString("clientToken", assignment.client_token);
+ dict.SetString("host", assignment.ip_endpoint.address().ToString());
+ dict.SetInteger("port", assignment.ip_endpoint.port());
+ dict.SetString("certificateFingerprint", assignment.certificate_fingerprint);
+ dict.SetString("certificate", assignment.certificate);
+
+ std::string json;
+ base::JSONWriter::Write(dict, &json);
+ return json;
+}
+
+class AssignmentSourceTest : public testing::Test {
+ public:
+ AssignmentSourceTest()
+ : task_runner_(new base::TestSimpleTaskRunner),
+ task_runner_handle_(task_runner_),
+ source_(task_runner_, task_runner_) {}
+
+ // This expects the AssignmentSource::GetAssignment to return a custom
+ // endpoint without having to hit the network. This will typically be used
+ // for testing that specifying an assignment via the command line works as
+ // expected.
+ void GetAlternateAssignment() {
+ source_.GetAssignment("",
+ base::Bind(&AssignmentSourceTest::AssignmentResponse,
+ base::Unretained(this)));
+ EXPECT_EQ(nullptr, factory_.GetFetcherByID(0));
+ task_runner_->RunUntilIdle();
+ }
+
+ // See net/base/net_errors.h for possible status errors.
+ void GetNetworkAssignmentAndWaitForResponse(
+ net::HttpStatusCode response_code,
+ int status,
+ const std::string& response,
+ const std::string& client_auth_token,
+ const std::string& protocol_version) {
+ source_.GetAssignment(client_auth_token,
+ base::Bind(&AssignmentSourceTest::AssignmentResponse,
+ base::Unretained(this)));
+
+ net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0);
+
+ task_runner_->RunUntilIdle();
+
+ EXPECT_NE(nullptr, fetcher);
+ EXPECT_EQ(kDefaultAssignerURL, fetcher->GetOriginalURL().spec());
+
+ // Check that the request has a valid protocol_version.
+ scoped_ptr<base::Value> json =
+ base::JSONReader::Read(fetcher->upload_data());
+ EXPECT_NE(nullptr, json.get());
+
+ const base::DictionaryValue* dict;
+ EXPECT_TRUE(json->GetAsDictionary(&dict));
+
+ std::string uploaded_protocol_version;
+ EXPECT_TRUE(
+ dict->GetString("protocol_version", &uploaded_protocol_version));
+ EXPECT_EQ(protocol_version, uploaded_protocol_version);
+
+ // Check that the request has a valid authentication header.
+ net::HttpRequestHeaders headers;
+ fetcher->GetExtraRequestHeaders(&headers);
+
+ std::string authorization;
+ EXPECT_TRUE(headers.GetHeader("Authorization", &authorization));
+ EXPECT_EQ("Bearer " + client_auth_token, authorization);
+
+ // Send the fake response back.
+ fetcher->set_response_code(response_code);
+ fetcher->set_status(net::URLRequestStatus::FromError(status));
+ fetcher->SetResponseString(response);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+
+ task_runner_->RunUntilIdle();
+ }
+
+ MOCK_METHOD2(AssignmentResponse,
+ void(AssignmentSource::Result, const Assignment&));
+
+ protected:
+ // Used to drive all AssignmentSource tasks.
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle task_runner_handle_;
+
+ net::TestURLFetcherFactory factory_;
+
+ AssignmentSource source_;
+};
+
+TEST_F(AssignmentSourceTest, TestTCPAlternateEndpointSuccess) {
+ Assignment assignment;
+ assignment.transport_protocol = Assignment::TransportProtocol::TCP;
+ assignment.ip_endpoint = BuildIPEndPoint("100.150.200.250", 500);
+ assignment.client_token = kDummyClientToken;
+
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kBlimpletEndpoint, "tcp:100.150.200.250:500");
+
+ EXPECT_CALL(*this, AssignmentResponse(AssignmentSource::Result::RESULT_OK,
+ AssignmentEquals(assignment)))
+ .Times(1);
+
+ GetAlternateAssignment();
+}
+
+TEST_F(AssignmentSourceTest, TestSSLAlternateEndpointSuccess) {
+ Assignment assignment;
+ assignment.transport_protocol = Assignment::TransportProtocol::SSL;
+ assignment.ip_endpoint = BuildIPEndPoint("100.150.200.250", 500);
+ assignment.client_token = kDummyClientToken;
+
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kBlimpletEndpoint, "ssl:100.150.200.250:500");
+
+ EXPECT_CALL(*this, AssignmentResponse(AssignmentSource::Result::RESULT_OK,
+ AssignmentEquals(assignment)))
+ .Times(1);
+
+ GetAlternateAssignment();
+}
+
+TEST_F(AssignmentSourceTest, TestQUICAlternateEndpointSuccess) {
+ Assignment assignment;
+ assignment.transport_protocol = Assignment::TransportProtocol::QUIC;
+ assignment.ip_endpoint = BuildIPEndPoint("100.150.200.250", 500);
+ assignment.client_token = kDummyClientToken;
+
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kBlimpletEndpoint, "quic:100.150.200.250:500");
+
+ EXPECT_CALL(*this, AssignmentResponse(AssignmentSource::Result::RESULT_OK,
+ AssignmentEquals(assignment)))
+ .Times(1);
+
+ GetAlternateAssignment();
+}
+
+TEST_F(AssignmentSourceTest, TestSuccess) {
+ Assignment assignment = BuildValidAssignment();
+
+ EXPECT_CALL(*this, AssignmentResponse(AssignmentSource::Result::RESULT_OK,
+ AssignmentEquals(assignment)))
+ .Times(1);
+
+ GetNetworkAssignmentAndWaitForResponse(
+ net::HTTP_OK, net::Error::OK, BuildResponseFromAssignment(assignment),
+ "UserAuthT0kenz", kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestSecondRequestInterruptsFirst) {
+ InSequence sequence;
+ Assignment assignment = BuildValidAssignment();
+
+ source_.GetAssignment("",
+ base::Bind(&AssignmentSourceTest::AssignmentResponse,
+ base::Unretained(this)));
+
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_SERVER_INTERRUPTED,
+ AssignmentEquals(Assignment())))
+ .Times(1)
+ .RetiresOnSaturation();
+
+ EXPECT_CALL(*this, AssignmentResponse(AssignmentSource::Result::RESULT_OK,
+ AssignmentEquals(assignment)))
+ .Times(1)
+ .RetiresOnSaturation();
+
+ GetNetworkAssignmentAndWaitForResponse(
+ net::HTTP_OK, net::Error::OK, BuildResponseFromAssignment(assignment),
+ "UserAuthT0kenz", kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestValidAfterError) {
+ InSequence sequence;
+ Assignment assignment = BuildValidAssignment();
+
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_NETWORK_FAILURE, _))
+ .Times(1)
+ .RetiresOnSaturation();
+
+ EXPECT_CALL(*this, AssignmentResponse(AssignmentSource::Result::RESULT_OK,
+ AssignmentEquals(assignment)))
+ .Times(1)
+ .RetiresOnSaturation();
+
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_OK,
+ net::Error::ERR_INSUFFICIENT_RESOURCES,
+ "", "UserAuthT0kenz", kEngineVersion);
+
+ GetNetworkAssignmentAndWaitForResponse(
+ net::HTTP_OK, net::Error::OK, BuildResponseFromAssignment(assignment),
+ "UserAuthT0kenz", kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestNetworkFailure) {
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_NETWORK_FAILURE, _));
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_OK,
+ net::Error::ERR_INSUFFICIENT_RESOURCES,
+ "", "UserAuthT0kenz", kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestBadRequest) {
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_BAD_REQUEST, _));
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_BAD_REQUEST, net::Error::OK,
+ "", "UserAuthT0kenz", kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestUnauthorized) {
+ EXPECT_CALL(*this,
+ AssignmentResponse(
+ AssignmentSource::Result::RESULT_EXPIRED_ACCESS_TOKEN, _));
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_UNAUTHORIZED, net::Error::OK,
+ "", "UserAuthT0kenz", kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestForbidden) {
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_USER_INVALID, _));
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_FORBIDDEN, net::Error::OK,
+ "", "UserAuthT0kenz", kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestTooManyRequests) {
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_OUT_OF_VMS, _));
+ GetNetworkAssignmentAndWaitForResponse(static_cast<net::HttpStatusCode>(429),
+ net::Error::OK, "", "UserAuthT0kenz",
+ kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestInternalServerError) {
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_SERVER_ERROR, _));
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_INTERNAL_SERVER_ERROR,
+ net::Error::OK, "", "UserAuthT0kenz",
+ kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestUnexpectedNetCodeFallback) {
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_BAD_RESPONSE, _));
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_NOT_IMPLEMENTED,
+ net::Error::OK, "", "UserAuthT0kenz",
+ kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestInvalidJsonResponse) {
+ Assignment assignment = BuildValidAssignment();
+
+ // Remove half the response.
+ std::string response = BuildResponseFromAssignment(assignment);
+ response = response.substr(response.size() / 2);
+
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_BAD_RESPONSE, _));
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_OK, net::Error::OK, response,
+ "UserAuthT0kenz", kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestMissingResponsePort) {
+ // Purposely do not add the 'port' field to the response.
+ base::DictionaryValue dict;
+ dict.SetString("clientToken", "SecretT0kenz");
+ dict.SetString("host", "happywhales");
+ dict.SetString("certificateFingerprint", "WhaleWhaleWhale");
+ dict.SetString("certificate", "whaaaaaaaaaaaaale");
+
+ std::string response;
+ base::JSONWriter::Write(dict, &response);
+
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_BAD_RESPONSE, _));
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_OK, net::Error::OK, response,
+ "UserAuthT0kenz", kEngineVersion);
+}
+
+TEST_F(AssignmentSourceTest, TestInvalidIPAddress) {
+ // Purposely add an invalid IP field to the response.
+ base::DictionaryValue dict;
+ dict.SetString("clientToken", "SecretT0kenz");
+ dict.SetString("host", "happywhales");
+ dict.SetInteger("port", 500);
+ dict.SetString("certificateFingerprint", "WhaleWhaleWhale");
+ dict.SetString("certificate", "whaaaaaaaaaaaaale");
+
+ std::string response;
+ base::JSONWriter::Write(dict, &response);
+
+ EXPECT_CALL(*this, AssignmentResponse(
+ AssignmentSource::Result::RESULT_BAD_RESPONSE, _));
+ GetNetworkAssignmentAndWaitForResponse(net::HTTP_OK, net::Error::OK, response,
+ "UserAuthT0kenz", kEngineVersion);
+}
+
+} // namespace
+} // namespace client
+} // namespace blimp
diff --git a/blimp/client/session/blimp_client_session.cc b/blimp/client/session/blimp_client_session.cc
index e681a0a..29c0b32 100644
--- a/blimp/client/session/blimp_client_session.cc
+++ b/blimp/client/session/blimp_client_session.cc
@@ -10,6 +10,7 @@
#include "base/command_line.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
+#include "base/thread_task_runner_handle.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "blimp/client/app/blimp_client_switches.h"
#include "blimp/client/feature/navigation_feature.h"
@@ -106,10 +107,8 @@ void ClientNetworkComponents::RegisterFeature(
outgoing_message_processors_.push_back(std::move(outgoing_message_processor));
}
-BlimpClientSession::BlimpClientSession(
- scoped_ptr<AssignmentSource> assignment_source)
- : assignment_source_(std::move(assignment_source)),
- io_thread_("BlimpIOThread"),
+BlimpClientSession::BlimpClientSession()
+ : io_thread_("BlimpIOThread"),
tab_control_feature_(new TabControlFeature),
navigation_feature_(new NavigationFeature),
render_widget_feature_(new RenderWidgetFeature),
@@ -119,6 +118,9 @@ BlimpClientSession::BlimpClientSession(
options.message_loop_type = base::MessageLoop::TYPE_IO;
io_thread_.StartWithOptions(options);
+ assignment_source_.reset(new AssignmentSource(
+ base::ThreadTaskRunnerHandle::Get(), io_thread_.task_runner()));
+
// Register features' message senders and receivers.
tab_control_feature_->set_outgoing_message_processor(
RegisterFeature(BlimpMessage::TAB_CONTROL, tab_control_feature_.get()));
@@ -144,18 +146,30 @@ BlimpClientSession::~BlimpClientSession() {
io_thread_.task_runner()->DeleteSoon(FROM_HERE, net_components_.release());
}
-void BlimpClientSession::Connect() {
- assignment_source_->GetAssignment(base::Bind(
- &BlimpClientSession::ConnectWithAssignment, weak_factory_.GetWeakPtr()));
+void BlimpClientSession::Connect(const std::string& client_auth_token) {
+ assignment_source_->GetAssignment(
+ client_auth_token, base::Bind(&BlimpClientSession::ConnectWithAssignment,
+ weak_factory_.GetWeakPtr()));
}
-void BlimpClientSession::ConnectWithAssignment(const Assignment& assignment) {
+void BlimpClientSession::ConnectWithAssignment(AssignmentSource::Result result,
+ const Assignment& assignment) {
+ OnAssignmentConnectionAttempted(result);
+
+ if (result != AssignmentSource::Result::RESULT_OK) {
+ VLOG(1) << "Assignment request failed: " << result;
+ return;
+ }
+
io_thread_.task_runner()->PostTask(
FROM_HERE,
base::Bind(&ClientNetworkComponents::ConnectWithAssignment,
base::Unretained(net_components_.get()), assignment));
}
+void BlimpClientSession::OnAssignmentConnectionAttempted(
+ AssignmentSource::Result result) {}
+
scoped_ptr<BlimpMessageProcessor> BlimpClientSession::RegisterFeature(
BlimpMessage::Type type,
BlimpMessageProcessor* incoming_processor) {
diff --git a/blimp/client/session/blimp_client_session.h b/blimp/client/session/blimp_client_session.h
index afcfd53..b715453 100644
--- a/blimp/client/session/blimp_client_session.h
+++ b/blimp/client/session/blimp_client_session.h
@@ -42,19 +42,31 @@ class TabControlFeature;
// feature proxies must be interacted with on the UI thread.
class BLIMP_CLIENT_EXPORT BlimpClientSession {
public:
- explicit BlimpClientSession(scoped_ptr<AssignmentSource> assignment_source);
+ BlimpClientSession();
// Uses the AssignmentSource to get an Assignment and then uses the assignment
// configuration to connect to the Blimplet.
- void Connect();
+ // |client_auth_token| is the OAuth2 access token to use when querying
+ // for an assignment. This token needs the OAuth2 scope of userinfo.email and
+ // only needs to be an access token, not a refresh token.
+ void Connect(const std::string& client_auth_token);
TabControlFeature* GetTabControlFeature() const;
NavigationFeature* GetNavigationFeature() const;
RenderWidgetFeature* GetRenderWidgetFeature() const;
+ // The AssignmentCallback for when an assignment is ready. This will trigger
+ // a connection to the engine.
+ virtual void ConnectWithAssignment(AssignmentSource::Result result,
+ const Assignment& assignment);
+
protected:
virtual ~BlimpClientSession();
+ // Notified every time the AssignmentSource returns the result of an attempted
+ // assignment request.
+ virtual void OnAssignmentConnectionAttempted(AssignmentSource::Result result);
+
private:
// Registers a message processor which will receive all messages of the |type|
// specified. Returns a BlimpMessageProcessor object for sending messages of
@@ -63,19 +75,15 @@ class BLIMP_CLIENT_EXPORT BlimpClientSession {
BlimpMessage::Type type,
BlimpMessageProcessor* incoming_processor);
- // The AssignmentCallback for when an assignment is ready. This will trigger
- // a connection to the engine.
- void ConnectWithAssignment(const Assignment& assignment);
-
- // The AssignmentSource is used when the user of BlimpClientSession calls
- // Connect() to get a valid assignment and later connect to the engine.
- scoped_ptr<AssignmentSource> assignment_source_;
-
base::Thread io_thread_;
scoped_ptr<TabControlFeature> tab_control_feature_;
scoped_ptr<NavigationFeature> navigation_feature_;
scoped_ptr<RenderWidgetFeature> render_widget_feature_;
+ // The AssignmentSource is used when the user of BlimpClientSession calls
+ // Connect() to get a valid assignment and later connect to the engine.
+ scoped_ptr<AssignmentSource> assignment_source_;
+
// Container struct for network components.
// Must be deleted on the IO thread.
scoped_ptr<ClientNetworkComponents> net_components_;
diff --git a/blimp/common/protocol_version.h b/blimp/common/protocol_version.h
index 98a7e63..ea628a2 100644
--- a/blimp/common/protocol_version.h
+++ b/blimp/common/protocol_version.h
@@ -11,6 +11,9 @@ namespace blimp {
const int BLIMP_COMMON_EXPORT kProtocolVersion = 0;
+// TODO(dtrainor): Move this out or integrate this with kProtocolVersion?
+const char BLIMP_COMMON_EXPORT kEngineVersion[] = "20160209094838";
+
} // namespace blimp
#endif // BLIMP_COMMON_PROTOCOL_VERSION_H_
diff --git a/blimp/docs/running.md b/blimp/docs/running.md
index 9678a09..f4a1d96 100644
--- a/blimp/docs/running.md
+++ b/blimp/docs/running.md
@@ -21,6 +21,11 @@ Set up any command line flags with:
./build/android/adb_blimp_command_line --enable-webgl
```
+To have the client connect to a custom engine use the `--blimplet-endpoint`
+flag. This takes values in the form of scheme:ip:port. The possible valid
+schemes are 'tcp', 'quic', and 'ssl'. An example valid endpoint would be
+`--blimplet-endpoint=tcp:127.0.0.1:500`.
+
Run the Blimp APK with:
```bash
@@ -46,3 +51,4 @@ started. You'll probably want to remap 25467 to "localhost:25467".
### Required flags
* `--blimp-client-token-path=$PATH`: Path to a file containing a nonempty
token string. If this is not present, the engine will fail to boot.
+