aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/app/src/main/java/com/wireguard/android/model
diff options
context:
space:
mode:
authorSamuel Holland <samuel@sholland.org>2018-01-01 02:06:37 -0600
committerSamuel Holland <samuel@sholland.org>2018-01-06 04:09:29 -0600
commit839576738819b2fbd7bce69dbe32af9f415af384 (patch)
tree96a7cd9846a093dfcdacfef285b0a4d77000edf0 /app/src/main/java/com/wireguard/android/model
parentRename package widgets -> widget (diff)
downloadwireguard-android-839576738819b2fbd7bce69dbe32af9f415af384.tar.xz
wireguard-android-839576738819b2fbd7bce69dbe32af9f415af384.zip
Serviceless rewrite, part 1
Signed-off-by: Samuel Holland <samuel@sholland.org>
Diffstat (limited to 'app/src/main/java/com/wireguard/android/model')
-rw-r--r--app/src/main/java/com/wireguard/android/model/Tunnel.java166
-rw-r--r--app/src/main/java/com/wireguard/android/model/TunnelCollection.java10
-rw-r--r--app/src/main/java/com/wireguard/android/model/TunnelManager.java111
3 files changed, 287 insertions, 0 deletions
diff --git a/app/src/main/java/com/wireguard/android/model/Tunnel.java b/app/src/main/java/com/wireguard/android/model/Tunnel.java
new file mode 100644
index 00000000..b196eaa5
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/model/Tunnel.java
@@ -0,0 +1,166 @@
+package com.wireguard.android.model;
+
+import android.databinding.BaseObservable;
+import android.databinding.Bindable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.wireguard.android.BR;
+import com.wireguard.android.backend.Backend;
+import com.wireguard.android.configStore.ConfigStore;
+import com.wireguard.android.util.ExceptionLoggers;
+import com.wireguard.config.Config;
+
+import org.threeten.bp.Instant;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+import java9.util.concurrent.CompletableFuture;
+import java9.util.concurrent.CompletionStage;
+
+/**
+ * Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
+ */
+
+public class Tunnel extends BaseObservable implements Comparable<Tunnel> {
+ public static final int NAME_MAX_LENGTH = 16;
+ private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,16}");
+ private static final String TAG = Tunnel.class.getSimpleName();
+
+ private final Backend backend;
+ private final ConfigStore configStore;
+ private final String name;
+ private Config config;
+ private Instant lastStateChange = Instant.EPOCH;
+ private State state = State.UNKNOWN;
+ private Statistics statistics;
+
+ Tunnel(@NonNull final Backend backend, @NonNull final ConfigStore configStore,
+ @NonNull final String name, @Nullable final Config config) {
+ this.backend = backend;
+ this.configStore = configStore;
+ this.name = name;
+ this.config = config;
+ }
+
+ public static boolean isNameValid(final CharSequence name) {
+ return name != null && NAME_PATTERN.matcher(name).matches();
+ }
+
+ @Override
+ public int compareTo(@NonNull final Tunnel tunnel) {
+ return name.compareTo(tunnel.name);
+ }
+
+ @Bindable
+ public Config getConfig() {
+ if (config == null)
+ getConfigAsync().whenComplete(ExceptionLoggers.D);
+ return config;
+ }
+
+ public CompletionStage<Config> getConfigAsync() {
+ if (config == null)
+ return configStore.load(name).thenApply(this::setConfigInternal);
+ return CompletableFuture.completedFuture(config);
+ }
+
+ @Bindable
+ public Instant getLastStateChange() {
+ return lastStateChange;
+ }
+
+ @Bindable
+ public String getName() {
+ return name;
+ }
+
+ @Bindable
+ public State getState() {
+ if (state == State.UNKNOWN)
+ getStateAsync().whenComplete(ExceptionLoggers.D);
+ return state;
+ }
+
+ public CompletionStage<State> getStateAsync() {
+ if (state == State.UNKNOWN)
+ return backend.getState(this).thenApply(this::setStateInternal);
+ return CompletableFuture.completedFuture(state);
+ }
+
+ @Bindable
+ public Statistics getStatistics() {
+ // FIXME: Check age of statistics.
+ if (statistics == null)
+ getStatisticsAsync().whenComplete(ExceptionLoggers.D);
+ return statistics;
+ }
+
+ public CompletionStage<Statistics> getStatisticsAsync() {
+ // FIXME: Check age of statistics.
+ if (statistics == null)
+ return backend.getStatistics(this).thenApply(this::setStatisticsInternal);
+ return CompletableFuture.completedFuture(statistics);
+ }
+
+ private void onStateChanged(final State oldState, final State newState) {
+ if (oldState != State.UNKNOWN) {
+ lastStateChange = Instant.now();
+ notifyPropertyChanged(BR.lastStateChange);
+ }
+ if (newState != State.UP)
+ setStatisticsInternal(null);
+ }
+
+ public CompletionStage<Config> setConfig(@NonNull final Config config) {
+ if (!config.equals(this.config)) {
+ return backend.applyConfig(this, config)
+ .thenCompose(cfg -> configStore.save(name, cfg))
+ .thenApply(this::setConfigInternal);
+ }
+ return CompletableFuture.completedFuture(this.config);
+ }
+
+ private Config setConfigInternal(final Config config) {
+ if (Objects.equals(this.config, config))
+ return config;
+ this.config = config;
+ notifyPropertyChanged(BR.config);
+ return config;
+ }
+
+ public CompletionStage<State> setState(@NonNull final State state) {
+ if (state != this.state)
+ return backend.setState(this, state)
+ .thenApply(this::setStateInternal);
+ return CompletableFuture.completedFuture(this.state);
+ }
+
+ private State setStateInternal(final State state) {
+ if (Objects.equals(this.state, state))
+ return state;
+ onStateChanged(this.state, state);
+ this.state = state;
+ notifyPropertyChanged(BR.state);
+ return state;
+ }
+
+ private Statistics setStatisticsInternal(final Statistics statistics) {
+ if (Objects.equals(this.statistics, statistics))
+ return statistics;
+ this.statistics = statistics;
+ notifyPropertyChanged(BR.statistics);
+ return statistics;
+ }
+
+ public enum State {
+ DOWN,
+ TOGGLE,
+ UNKNOWN,
+ UP
+ }
+
+ public static class Statistics extends BaseObservable {
+ }
+}
diff --git a/app/src/main/java/com/wireguard/android/model/TunnelCollection.java b/app/src/main/java/com/wireguard/android/model/TunnelCollection.java
new file mode 100644
index 00000000..38b5165a
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/model/TunnelCollection.java
@@ -0,0 +1,10 @@
+package com.wireguard.android.model;
+
+import com.wireguard.android.databinding.ObservableTreeMap;
+
+/**
+ * Created by samuel on 12/19/17.
+ */
+
+public class TunnelCollection extends ObservableTreeMap<String, Tunnel> {
+}
diff --git a/app/src/main/java/com/wireguard/android/model/TunnelManager.java b/app/src/main/java/com/wireguard/android/model/TunnelManager.java
new file mode 100644
index 00000000..5122f9bf
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/model/TunnelManager.java
@@ -0,0 +1,111 @@
+package com.wireguard.android.model;
+
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.wireguard.android.Application.ApplicationScope;
+import com.wireguard.android.backend.Backend;
+import com.wireguard.android.configStore.ConfigStore;
+import com.wireguard.android.model.Tunnel.State;
+import com.wireguard.android.util.ExceptionLoggers;
+import com.wireguard.config.Config;
+
+import java.util.Collections;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import java9.util.concurrent.CompletableFuture;
+import java9.util.concurrent.CompletionStage;
+import java9.util.stream.Collectors;
+import java9.util.stream.StreamSupport;
+
+/**
+ * Maintains and mediates changes to the set of available WireGuard tunnels,
+ */
+
+@ApplicationScope
+public final class TunnelManager {
+ public static final String KEY_PRIMARY_TUNNEL = "primary_config";
+ public static final String KEY_SELECTED_TUNNEL = "selected_tunnel";
+ private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
+ private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
+ private static final String TAG = TunnelManager.class.getSimpleName();
+
+ private final Backend backend;
+ private final ConfigStore configStore;
+ private final SharedPreferences preferences;
+ private final TunnelCollection tunnels = new TunnelCollection();
+
+ @Inject
+ public TunnelManager(final Backend backend, final ConfigStore configStore,
+ final SharedPreferences preferences) {
+ this.backend = backend;
+ this.configStore = configStore;
+ this.preferences = preferences;
+ }
+
+ private Tunnel add(final String name, final Config config) {
+ final Tunnel tunnel = new Tunnel(backend, configStore, name, config);
+ tunnels.put(name, tunnel);
+ return tunnel;
+ }
+
+ private Tunnel add(final String name) {
+ return add(name, null);
+ }
+
+ public CompletionStage<Tunnel> create(final String name, final Config config) {
+ Log.v(TAG, "Requested create tunnel " + name + " with config\n" + config);
+ if (!Tunnel.isNameValid(name))
+ return CompletableFuture.failedFuture(new IllegalArgumentException("Invalid name"));
+ if (tunnels.containsKey(name)) {
+ final String message = "Tunnel " + name + " already exists";
+ return CompletableFuture.failedFuture(new IllegalArgumentException(message));
+ }
+ return configStore.create(name, config).thenApply(savedConfig -> add(name, savedConfig));
+ }
+
+ public CompletionStage<Void> delete(final Tunnel tunnel) {
+ Log.v(TAG, "Requested delete tunnel " + tunnel.getName() + " state=" + tunnel.getState());
+ return backend.setState(tunnel, State.DOWN)
+ .thenCompose(x -> configStore.delete(tunnel.getName()))
+ .thenAccept(x -> tunnels.remove(tunnel.getName()));
+ }
+
+ public TunnelCollection getTunnels() {
+ return tunnels;
+ }
+
+ public void onCreate() {
+ Log.v(TAG, "onCreate triggered");
+ configStore.enumerate()
+ .thenApply(names -> StreamSupport.stream(names)
+ .map(this::add)
+ .map(Tunnel::getStateAsync)
+ .toArray(CompletableFuture[]::new))
+ .thenCompose(CompletableFuture::allOf)
+ .whenComplete(ExceptionLoggers.E);
+ }
+
+ public CompletionStage<Void> restoreState() {
+ if (!preferences.getBoolean(KEY_RESTORE_ON_BOOT, false))
+ return CompletableFuture.completedFuture(null);
+ final Set<String> tunnelsToEnable =
+ preferences.getStringSet(KEY_RUNNING_TUNNELS, Collections.emptySet());
+ final CompletableFuture[] futures = StreamSupport.stream(tunnelsToEnable)
+ .map(tunnels::get)
+ .map(tunnel -> tunnel.setState(State.UP))
+ .toArray(CompletableFuture[]::new);
+ return CompletableFuture.allOf(futures);
+ }
+
+ public CompletionStage<Void> saveState() {
+ final Set<String> runningTunnels = StreamSupport.stream(tunnels.values())
+ .filter(tunnel -> tunnel.getState() == State.UP)
+ .map(Tunnel::getName)
+ .collect(Collectors.toUnmodifiableSet());
+ preferences.edit().putStringSet(KEY_RUNNING_TUNNELS, runningTunnels).apply();
+ return CompletableFuture.completedFuture(null);
+ }
+}