diff options
Diffstat (limited to 'app/src/main/java/com/wireguard/android/model/Tunnel.java')
-rw-r--r-- | app/src/main/java/com/wireguard/android/model/Tunnel.java | 166 |
1 files changed, 166 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 { + } +} |