diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-05-22 16:40:14 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-05-22 16:40:14 -0300 |
| commit | 11a05dad217354464ce8f47c2b66cb4851e090f1 (patch) | |
| tree | 2e5aea78112e19f15becc3769b7519963875a478 | |
| parent | 1d9a3f9c6a3bc536de0a92654d87f982ef2bb219 (diff) | |
| parent | 51eed2f2ed26592141f741d680241d26a79306a4 (diff) | |
| download | mullvadvpn-11a05dad217354464ce8f47c2b66cb4851e090f1.tar.xz mullvadvpn-11a05dad217354464ce8f47c2b66cb4851e090f1.zip | |
Merge branch 'login-on-android'
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/LoginFragment.kt | 33 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt | 4 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountData.kt | 3 | ||||
| -rw-r--r-- | mullvad-jni/Cargo.toml | 2 | ||||
| -rw-r--r-- | mullvad-jni/src/daemon_interface.rs | 38 | ||||
| -rw-r--r-- | mullvad-jni/src/from_java.rs | 18 | ||||
| -rw-r--r-- | mullvad-jni/src/into_java.rs | 33 | ||||
| -rw-r--r-- | mullvad-jni/src/lib.rs | 72 |
9 files changed, 195 insertions, 10 deletions
diff --git a/Cargo.lock b/Cargo.lock index 60bfb33769..0744c310ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1165,7 +1165,9 @@ name = "mullvad-jni" version = "0.1.0" dependencies = [ "err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "jni 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-client-core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log-panics 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/LoginFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/LoginFragment.kt index 9159c93079..3ead35333c 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/LoginFragment.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/LoginFragment.kt @@ -1,5 +1,12 @@ package net.mullvad.mullvadvpn +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job + import android.os.Bundle import android.os.Handler import android.support.v4.app.Fragment @@ -16,6 +23,8 @@ class LoginFragment : Fragment() { private lateinit var loginFailStatus: View private lateinit var accountInput: AccountInput + private var loginJob: Deferred<Boolean>? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -45,11 +54,27 @@ class LoginFragment : Fragment() { accountInput.state = LoginState.InProgress - // TODO: Actually log in - if ("1234567890".equals(accountToken)) { - Handler().postDelayed(Runnable { loggedIn() }, 1000) + performLogin(accountToken) + } + + private fun performLogin(accountToken: String) = GlobalScope.launch(Dispatchers.Main) { + loginJob?.cancel() + loginJob = GlobalScope.async(Dispatchers.Default) { + val parentActivity = activity as MainActivity + val daemon = parentActivity.asyncDaemon.await() + val accountData = daemon.getAccountData(accountToken) + + if (accountData != null) { + true + } else { + false + } + } + + if (loginJob?.await() ?: false) { + loggedIn() } else { - Handler().postDelayed(Runnable { loginFailure() }, 1000) + loginFailure() } } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt index d5ab845f89..c7025ea644 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt @@ -1,10 +1,14 @@ package net.mullvad.mullvadvpn +import net.mullvad.mullvadvpn.model.AccountData + class MullvadDaemon { init { System.loadLibrary("mullvad_jni") initialize() } + external fun getAccountData(accountToken: String): AccountData? + private external fun initialize() } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountData.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountData.kt new file mode 100644 index 0000000000..6dda6b8352 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountData.kt @@ -0,0 +1,3 @@ +package net.mullvad.mullvadvpn.model + +data class AccountData(val expiry: String) diff --git a/mullvad-jni/Cargo.toml b/mullvad-jni/Cargo.toml index 9b87aa07a3..a9b8092d92 100644 --- a/mullvad-jni/Cargo.toml +++ b/mullvad-jni/Cargo.toml @@ -11,7 +11,9 @@ crate_type = ["cdylib"] [dependencies] err-derive = "0.1.5" +futures = "0.1" jni = "0.11" +jsonrpc-client-core = "0.5" lazy_static = "1" log = "0.4" log-panics = "2" diff --git a/mullvad-jni/src/daemon_interface.rs b/mullvad-jni/src/daemon_interface.rs index b682660edf..6b0e907eda 100644 --- a/mullvad-jni/src/daemon_interface.rs +++ b/mullvad-jni/src/daemon_interface.rs @@ -1,4 +1,23 @@ -use mullvad_daemon::DaemonCommandSender; +use futures::{sync::oneshot, Future}; +use mullvad_daemon::{DaemonCommandSender, ManagementCommand}; +use mullvad_types::account::AccountData; + +#[derive(Debug, err_derive::Error)] +pub enum Error { + #[error(display = "Can't send command to daemon because it is not running")] + NoDaemon(#[error(cause)] mullvad_daemon::Error), + + #[error(display = "No response received from daemon")] + NoResponse, + + #[error(display = "Attempt to use daemon command sender before it was configured")] + NoSender, + + #[error(display = "Error performing RPC with the remote API")] + RpcError(#[error(cause)] jsonrpc_client_core::Error), +} + +type Result<T> = std::result::Result<T, Error>; pub struct DaemonInterface { command_sender: Option<DaemonCommandSender>, @@ -14,4 +33,21 @@ impl DaemonInterface { pub fn set_command_sender(&mut self, sender: DaemonCommandSender) { self.command_sender = Some(sender); } + + pub fn get_account_data(&self, account_token: String) -> Result<AccountData> { + let (tx, rx) = oneshot::channel(); + + self.send_command(ManagementCommand::GetAccountData(tx, account_token))?; + + rx.wait() + .map_err(|_| Error::NoResponse)? + .wait() + .map_err(Error::RpcError) + } + + fn send_command(&self, command: ManagementCommand) -> Result<()> { + let sender = self.command_sender.as_ref().ok_or(Error::NoSender)?; + + sender.send(command).map_err(Error::NoDaemon) + } } diff --git a/mullvad-jni/src/from_java.rs b/mullvad-jni/src/from_java.rs new file mode 100644 index 0000000000..526a83e1fb --- /dev/null +++ b/mullvad-jni/src/from_java.rs @@ -0,0 +1,18 @@ +use jni::{objects::JString, JNIEnv}; + +pub trait FromJava<'env> { + type JavaType: 'env; + + fn from_java(env: &JNIEnv<'env>, source: Self::JavaType) -> Self; +} + +impl<'env> FromJava<'env> for String { + type JavaType = JString<'env>; + + fn from_java(env: &JNIEnv<'env>, source: Self::JavaType) -> Self { + String::from( + env.get_string(source) + .expect("Failed to convert from Java String"), + ) + } +} diff --git a/mullvad-jni/src/into_java.rs b/mullvad-jni/src/into_java.rs new file mode 100644 index 0000000000..952338ccb7 --- /dev/null +++ b/mullvad-jni/src/into_java.rs @@ -0,0 +1,33 @@ +use crate::get_class; +use jni::{ + objects::{JObject, JString, JValue}, + JNIEnv, +}; +use mullvad_types::account::AccountData; + +pub trait IntoJava<'env> { + type JavaType; + + fn into_java(self, env: &JNIEnv<'env>) -> Self::JavaType; +} + +impl<'env> IntoJava<'env> for String { + type JavaType = JString<'env>; + + fn into_java(self, env: &JNIEnv<'env>) -> Self::JavaType { + env.new_string(&self).expect("Failed to create Java String") + } +} + +impl<'env> IntoJava<'env> for AccountData { + type JavaType = JObject<'env>; + + fn into_java(self, env: &JNIEnv<'env>) -> Self::JavaType { + let class = get_class("net/mullvad/mullvadvpn/model/AccountData"); + let account_expiry = env.auto_local(JObject::from(self.expiry.to_string().into_java(env))); + let parameters = [JValue::Object(account_expiry.as_obj())]; + + env.new_object(&class, "(Ljava/lang/String;)V", ¶meters) + .expect("Failed to create AccountData Java object") + } +} diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs index ce6d953fcd..35d3825dcf 100644 --- a/mullvad-jni/src/lib.rs +++ b/mullvad-jni/src/lib.rs @@ -1,20 +1,29 @@ #![cfg(target_os = "android")] mod daemon_interface; +mod from_java; +mod into_java; -use crate::daemon_interface::DaemonInterface; -use jni::{objects::JObject, JNIEnv}; +use crate::{daemon_interface::DaemonInterface, from_java::FromJava, into_java::IntoJava}; +use jni::{ + objects::{GlobalRef, JObject, JString}, + JNIEnv, +}; use lazy_static::lazy_static; use mullvad_daemon::{logging, version, Daemon, DaemonCommandSender, EventListener}; use mullvad_types::{relay_list::RelayList, settings::Settings}; -use parking_lot::Mutex; -use std::{path::PathBuf, sync::mpsc, thread}; +use parking_lot::{Mutex, RwLock}; +use std::{collections::HashMap, path::PathBuf, sync::mpsc, thread}; use talpid_types::{tunnel::TunnelStateTransition, ErrorExt}; const LOG_FILENAME: &str = "daemon.log"; +const CLASSES_TO_LOAD: &[&str] = &["net/mullvad/mullvadvpn/model/AccountData"]; + lazy_static! { static ref DAEMON_INTERFACE: Mutex<DaemonInterface> = Mutex::new(DaemonInterface::new()); + static ref CLASSES: RwLock<HashMap<&'static str, GlobalRef>> = + RwLock::new(HashMap::with_capacity(CLASSES_TO_LOAD.len())); } #[derive(Debug, err_derive::Error)] @@ -28,9 +37,14 @@ pub enum Error { #[no_mangle] #[allow(non_snake_case)] -pub extern "system" fn Java_net_mullvad_mullvadvpn_MullvadDaemon_initialize(_: JNIEnv, _: JObject) { +pub extern "system" fn Java_net_mullvad_mullvadvpn_MullvadDaemon_initialize( + env: JNIEnv, + _: JObject, +) { let log_dir = start_logging(); + load_classes(&env); + if let Err(error) = initialize(log_dir) { log::error!("{}", error.display_chain()); } @@ -47,6 +61,24 @@ fn start_logging() -> PathBuf { log_dir } +fn load_classes(env: &JNIEnv) { + let mut classes = CLASSES.write(); + + for class in CLASSES_TO_LOAD { + classes.insert(class, load_class_reference(env, class)); + } +} + +fn load_class_reference(env: &JNIEnv, name: &str) -> GlobalRef { + let class = match env.find_class(name) { + Ok(class) => class, + Err(_) => panic!("Failed to find {} Java class", name), + }; + + env.new_global_ref(JObject::from(class)) + .expect("Failed to convert local reference to Java class into a global reference") +} + fn initialize(log_dir: PathBuf) -> Result<(), Error> { let daemon_command_sender = spawn_daemon(log_dir)?; @@ -100,3 +132,33 @@ impl EventListener for DummyListener { fn notify_settings(&self, _: Settings) {} fn notify_relay_list(&self, _: RelayList) {} } + +fn get_class(name: &str) -> GlobalRef { + match CLASSES.read().get(name) { + Some(class) => class.clone(), + None => panic!("Class not loaded: {}", name), + } +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn Java_net_mullvad_mullvadvpn_MullvadDaemon_getAccountData<'env, 'this>( + env: JNIEnv<'env>, + _: JObject<'this>, + accountToken: JString, +) -> JObject<'env> { + let daemon = DAEMON_INTERFACE.lock(); + + let account = String::from_java(&env, accountToken); + + match daemon.get_account_data(account) { + Ok(data) => data.into_java(&env), + Err(error) => { + log::error!( + "{}", + error.display_chain_with_msg("Failed to get account data") + ); + JObject::null() + } + } +} |
