diff options
author | dtrainor <dtrainor@chromium.org> | 2016-02-19 08:10:25 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-19 16:11:31 +0000 |
commit | 6df6164eda86c8ccdb3a0aad8025d9d2a827ee10 (patch) | |
tree | f6d6887f207b63f301e99c94873e74d3f52cdf9d /blimp | |
parent | 9352dd94f89103fba52d018f24d657e7884fda5d (diff) | |
download | chromium_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')
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. + |