summaryrefslogtreecommitdiffhomepage
path: root/app/src/main/java/com/wireguard/android
diff options
context:
space:
mode:
authorSamuel Holland <samuel@sholland.org>2018-09-05 20:17:14 -0500
committerJason A. Donenfeld <Jason@zx2c4.com>2018-12-08 02:39:41 +0100
commitd1e85633fbe8d871355d2b9feb51e2c9983d8a21 (patch)
treed95ad1ae84d02fc3e18a211aa1e1ef8150d8fa35 /app/src/main/java/com/wireguard/android
parenta264f7ab36bf1335999d53cb4a0d753c54b231d0 (diff)
Remodel the Model
- The configuration and crypto model is now entirely independent of Android classes other than Nullable and TextUtils. - Model classes are immutable and use builders that enforce the appropriate optional/required attributes. - The Android config proxies (for Parcelable and databinding) are moved to the Android side of the codebase, and are designed to be safe for two-way databinding. This allows proper observability in TunnelDetailFragment. - Various robustness fixes and documentation updates to helper classes. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'app/src/main/java/com/wireguard/android')
-rw-r--r--app/src/main/java/com/wireguard/android/backend/GoBackend.java36
-rw-r--r--app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java2
-rw-r--r--app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java9
-rw-r--r--app/src/main/java/com/wireguard/android/databinding/BindingAdapters.java52
-rw-r--r--app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java2
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java17
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java7
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java9
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java115
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java14
-rw-r--r--app/src/main/java/com/wireguard/android/model/ApplicationData.java1
-rw-r--r--app/src/main/java/com/wireguard/android/model/Tunnel.java6
-rw-r--r--app/src/main/java/com/wireguard/android/model/TunnelManager.java4
-rw-r--r--app/src/main/java/com/wireguard/android/preference/VersionPreference.java3
-rw-r--r--app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java41
-rw-r--r--app/src/main/java/com/wireguard/android/util/FragmentUtils.java1
-rw-r--r--app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java6
-rw-r--r--app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java3
-rw-r--r--app/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java93
-rw-r--r--app/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java189
-rw-r--r--app/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java379
-rw-r--r--app/src/main/java/com/wireguard/android/widget/KeyInputFilter.java11
-rw-r--r--app/src/main/java/com/wireguard/android/widget/NameInputFilter.java3
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java2
24 files changed, 830 insertions, 175 deletions
diff --git a/app/src/main/java/com/wireguard/android/backend/GoBackend.java b/app/src/main/java/com/wireguard/android/backend/GoBackend.java
index 295df9d0..97cf0f8e 100644
--- a/app/src/main/java/com/wireguard/android/backend/GoBackend.java
+++ b/app/src/main/java/com/wireguard/android/backend/GoBackend.java
@@ -22,13 +22,10 @@ import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.util.SharedLibraryLoader;
import com.wireguard.config.Config;
import com.wireguard.config.InetNetwork;
-import com.wireguard.config.Interface;
import com.wireguard.config.Peer;
-import com.wireguard.crypto.KeyEncoding;
import java.net.InetAddress;
import java.util.Collections;
-import java.util.Formatter;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -146,29 +143,7 @@ public final class GoBackend implements Backend {
}
// Build config
- final Interface iface = config.getInterface();
- final String goConfig;
- try (final Formatter fmt = new Formatter(new StringBuilder())) {
- fmt.format("replace_peers=true\n");
- if (iface.getPrivateKey() != null)
- fmt.format("private_key=%s\n", KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(iface.getPrivateKey())));
- if (iface.getListenPort() != 0)
- fmt.format("listen_port=%d\n", config.getInterface().getListenPort());
- for (final Peer peer : config.getPeers()) {
- if (peer.getPublicKey() != null)
- fmt.format("public_key=%s\n", KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(peer.getPublicKey())));
- if (peer.getPreSharedKey() != null)
- fmt.format("preshared_key=%s\n", KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(peer.getPreSharedKey())));
- if (peer.getEndpoint() != null)
- fmt.format("endpoint=%s\n", peer.getResolvedEndpointString());
- if (peer.getPersistentKeepalive() != 0)
- fmt.format("persistent_keepalive_interval=%d\n", peer.getPersistentKeepalive());
- for (final InetNetwork addr : peer.getAllowedIPs()) {
- fmt.format("allowed_ip=%s\n", addr.toString());
- }
- }
- goConfig = fmt.toString();
- }
+ final String goConfig = config.toWgUserspaceString();
// Create the vpn tunnel with android API
final VpnService.Builder builder = service.getBuilder();
@@ -184,18 +159,15 @@ public final class GoBackend implements Backend {
for (final InetNetwork addr : config.getInterface().getAddresses())
builder.addAddress(addr.getAddress(), addr.getMask());
- for (final InetAddress addr : config.getInterface().getDnses())
+ for (final InetAddress addr : config.getInterface().getDnsServers())
builder.addDnsServer(addr.getHostAddress());
for (final Peer peer : config.getPeers()) {
- for (final InetNetwork addr : peer.getAllowedIPs())
+ for (final InetNetwork addr : peer.getAllowedIps())
builder.addRoute(addr.getAddress(), addr.getMask());
}
- int mtu = config.getInterface().getMtu();
- if (mtu == 0)
- mtu = 1280;
- builder.setMtu(mtu);
+ builder.setMtu(config.getInterface().getMtu().orElse(1280));
builder.setBlocking(true);
try (final ParcelFileDescriptor tun = builder.establish()) {
diff --git a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
index bfc363a4..68799057 100644
--- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
+++ b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
@@ -114,7 +114,7 @@ public final class WgQuickBackend implements Backend {
final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf");
try (final FileOutputStream stream = new FileOutputStream(tempFile, false)) {
- stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
+ stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
}
String command = String.format("wg-quick %s '%s'",
state.toString().toLowerCase(), tempFile.getAbsolutePath());
diff --git a/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java b/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java
index 0e66dab8..654cb48f 100644
--- a/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java
+++ b/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java
@@ -9,6 +9,7 @@ import android.content.Context;
import android.util.Log;
import com.wireguard.config.Config;
+import com.wireguard.config.ParseException;
import java.io.File;
import java.io.FileInputStream;
@@ -41,7 +42,7 @@ public final class FileConfigStore implements ConfigStore {
if (!file.createNewFile())
throw new IOException("Configuration file " + file.getName() + " already exists");
try (final FileOutputStream stream = new FileOutputStream(file, false)) {
- stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
+ stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
}
return config;
}
@@ -67,9 +68,9 @@ public final class FileConfigStore implements ConfigStore {
}
@Override
- public Config load(final String name) throws IOException {
+ public Config load(final String name) throws IOException, ParseException {
try (final FileInputStream stream = new FileInputStream(fileFor(name))) {
- return Config.from(stream);
+ return Config.parse(stream);
}
}
@@ -94,7 +95,7 @@ public final class FileConfigStore implements ConfigStore {
if (!file.isFile())
throw new FileNotFoundException("Configuration file " + file.getName() + " not found");
try (final FileOutputStream stream = new FileOutputStream(file, false)) {
- stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
+ stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
}
return config;
}
diff --git a/app/src/main/java/com/wireguard/android/databinding/BindingAdapters.java b/app/src/main/java/com/wireguard/android/databinding/BindingAdapters.java
index 629f99e5..fe01bf10 100644
--- a/app/src/main/java/com/wireguard/android/databinding/BindingAdapters.java
+++ b/app/src/main/java/com/wireguard/android/databinding/BindingAdapters.java
@@ -6,21 +6,30 @@
package com.wireguard.android.databinding;
import android.databinding.BindingAdapter;
+import android.databinding.DataBindingUtil;
import android.databinding.ObservableList;
+import android.databinding.ViewDataBinding;
import android.databinding.adapters.ListenerUtil;
+import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.InputFilter;
+import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.wireguard.android.BR;
import com.wireguard.android.R;
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler;
import com.wireguard.android.util.ObservableKeyedList;
import com.wireguard.android.widget.ToggleSwitch;
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
+import com.wireguard.config.Attribute;
+import com.wireguard.config.InetNetwork;
import com.wireguard.util.Keyed;
+import java9.util.Optional;
+
/**
* Static methods for use by generated code in the Android data binding library.
*/
@@ -42,9 +51,10 @@ public final class BindingAdapters {
}
@BindingAdapter({"items", "layout"})
- public static <E> void setItems(final LinearLayout view,
- final ObservableList<E> oldList, final int oldLayoutId,
- final ObservableList<E> newList, final int newLayoutId) {
+ public static <E>
+ void setItems(final LinearLayout view,
+ @Nullable final ObservableList<E> oldList, final int oldLayoutId,
+ @Nullable final ObservableList<E> newList, final int newLayoutId) {
if (oldList == newList && oldLayoutId == newLayoutId)
return;
ItemChangeListener<E> listener = ListenerUtil.getListener(view, R.id.item_change_listener);
@@ -66,11 +76,34 @@ public final class BindingAdapters {
listener.setList(newList);
}
+ @BindingAdapter({"items", "layout"})
+ public static <E>
+ void setItems(final LinearLayout view,
+ @Nullable final Iterable<E> oldList, final int oldLayoutId,
+ @Nullable final Iterable<E> newList, final int newLayoutId) {
+ if (oldList == newList && oldLayoutId == newLayoutId)
+ return;
+ view.removeAllViews();
+ if (newList == null)
+ return;
+ final LayoutInflater layoutInflater = LayoutInflater.from(view.getContext());
+ for (final E item : newList) {
+ final ViewDataBinding binding =
+ DataBindingUtil.inflate(layoutInflater, newLayoutId, view, false);
+ binding.setVariable(BR.collection, newList);
+ binding.setVariable(BR.item, item);
+ binding.executePendingBindings();
+ view.addView(binding.getRoot());
+ }
+ }
+
@BindingAdapter(requireAll = false, value = {"items", "layout", "configurationHandler"})
public static <K, E extends Keyed<? extends K>>
void setItems(final RecyclerView view,
- final ObservableKeyedList<K, E> oldList, final int oldLayoutId, final RowConfigurationHandler oldRowConfigurationHandler,
- final ObservableKeyedList<K, E> newList, final int newLayoutId, final RowConfigurationHandler newRowConfigurationHandler) {
+ @Nullable final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
+ final RowConfigurationHandler oldRowConfigurationHandler,
+ @Nullable final ObservableKeyedList<K, E> newList, final int newLayoutId,
+ final RowConfigurationHandler newRowConfigurationHandler) {
if (view.getLayoutManager() == null)
view.setLayoutManager(new LinearLayoutManager(view.getContext(), RecyclerView.VERTICAL, false));
@@ -103,4 +136,13 @@ public final class BindingAdapters {
view.setOnBeforeCheckedChangeListener(listener);
}
+ @BindingAdapter("android:text")
+ public static void setText(final TextView view, final Optional<?> text) {
+ view.setText(text.map(Object::toString).orElse(""));
+ }
+
+ @BindingAdapter("android:text")
+ public static void setText(final TextView view, @Nullable final Iterable<InetNetwork> networks) {
+ view.setText(networks != null ? Attribute.join(networks) : "");
+ }
}
diff --git a/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java b/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java
index 26e7687f..5bfa64f1 100644
--- a/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java
+++ b/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java
@@ -73,7 +73,7 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
holder.binding.executePendingBindings();
if (rowConfigurationHandler != null) {
- E item = getItem(position);
+ final E item = getItem(position);
if (item != null) {
rowConfigurationHandler.onConfigureRow(holder.binding, item, position);
}
diff --git a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java
index 8bf5a22d..20633c3e 100644
--- a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java
+++ b/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java
@@ -27,19 +27,21 @@ import com.wireguard.android.util.ObservableKeyedArrayList;
import com.wireguard.android.util.ObservableKeyedList;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java9.util.Comparators;
+
public class AppListDialogFragment extends DialogFragment {
private static final String KEY_EXCLUDED_APPS = "excludedApps";
private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>();
- private List<String> currentlyExcludedApps;
+ @Nullable private List<String> currentlyExcludedApps;
- public static <T extends Fragment & AppExclusionListener> AppListDialogFragment newInstance(final String[] excludedApps, final T target) {
+ public static <T extends Fragment & AppExclusionListener>
+ AppListDialogFragment newInstance(final ArrayList<String> excludedApps, final T target) {
final Bundle extras = new Bundle();
- extras.putStringArray(KEY_EXCLUDED_APPS, excludedApps);
+ extras.putStringArrayList(KEY_EXCLUDED_APPS, excludedApps);
final AppListDialogFragment fragment = new AppListDialogFragment();
fragment.setTargetFragment(target, 0);
fragment.setArguments(extras);
@@ -64,7 +66,7 @@ public class AppListDialogFragment extends DialogFragment {
appData.add(new ApplicationData(resolveInfo.loadIcon(pm), resolveInfo.loadLabel(pm).toString(), packageName, currentlyExcludedApps.contains(packageName)));
}
- Collections.sort(appData, (lhs, rhs) -> lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase()));
+ Collections.sort(appData, Comparators.comparing(ApplicationData::getName, String.CASE_INSENSITIVE_ORDER));
return appData;
}).whenComplete(((data, throwable) -> {
if (data != null) {
@@ -82,12 +84,11 @@ public class AppListDialogFragment extends DialogFragment {
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS));
+ currentlyExcludedApps = getArguments().getStringArrayList(KEY_EXCLUDED_APPS);
}
@Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setTitle(R.string.excluded_applications);
diff --git a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java
index 83799818..0931868e 100644
--- a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java
+++ b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java
@@ -19,8 +19,11 @@ import com.wireguard.android.Application;
import com.wireguard.android.R;
import com.wireguard.android.databinding.ConfigNamingDialogFragmentBinding;
import com.wireguard.config.Config;
+import com.wireguard.config.ParseException;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Objects;
public class ConfigNamingDialogFragment extends DialogFragment {
@@ -63,8 +66,8 @@ public class ConfigNamingDialogFragment extends DialogFragment {
super.onCreate(savedInstanceState);
try {
- config = Config.from(getArguments().getString(KEY_CONFIG_TEXT));
- } catch (final IOException exception) {
+ config = Config.parse(new ByteArrayInputStream(getArguments().getString(KEY_CONFIG_TEXT).getBytes(StandardCharsets.UTF_8)));
+ } catch (final IOException | ParseException exception) {
throw new RuntimeException("Invalid config passed to " + getClass().getSimpleName(), exception);
}
}
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
index fcc601f3..b4e7202f 100644
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
+++ b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
@@ -16,7 +16,6 @@ import android.view.ViewGroup;
import com.wireguard.android.R;
import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
import com.wireguard.android.model.Tunnel;
-import com.wireguard.config.Config;
/**
* Fragment that shows details about a specific tunnel.
@@ -25,12 +24,6 @@ import com.wireguard.config.Config;
public class TunnelDetailFragment extends BaseFragment {
@Nullable private TunnelDetailFragmentBinding binding;
- private void onConfigLoaded(final String name, final Config config) {
- if (binding != null) {
- binding.setConfig(new Config.Observable(config, name));
- }
- }
-
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -65,7 +58,7 @@ public class TunnelDetailFragment extends BaseFragment {
if (newTunnel == null)
binding.setConfig(null);
else
- newTunnel.getConfigAsync().thenAccept(a -> onConfigLoaded(newTunnel.getName(), a));
+ newTunnel.getConfigAsync().thenAccept(binding::setConfig);
}
@Override
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java
index 8f319e1e..f1250e64 100644
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java
+++ b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java
@@ -7,7 +7,6 @@ package com.wireguard.android.fragment;
import android.app.Activity;
import android.content.Context;
-import android.databinding.Observable;
import android.databinding.ObservableList;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -24,19 +23,16 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import com.wireguard.android.Application;
-import com.wireguard.android.BR;
import com.wireguard.android.R;
import com.wireguard.android.databinding.TunnelEditorFragmentBinding;
import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.TunnelManager;
import com.wireguard.android.util.ExceptionLoggers;
-import com.wireguard.config.Attribute;
+import com.wireguard.android.viewmodel.ConfigProxy;
import com.wireguard.config.Config;
-import com.wireguard.config.Peer;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -48,64 +44,13 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
private static final String KEY_LOCAL_CONFIG = "local_config";
private static final String KEY_ORIGINAL_NAME = "original_name";
private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
- private final Collection<Object> breakObjectOrientedLayeringHandlerReceivers = new ArrayList<>();
- @Nullable private TunnelEditorFragmentBinding binding;
- private final Observable.OnPropertyChangedCallback breakObjectOrientedLayeringHandler = new Observable.OnPropertyChangedCallback() {
- @Override
- public void onPropertyChanged(final Observable sender, final int propertyId) {
- if (binding == null)
- return;
- final Config.Observable config = binding.getConfig();
- if (config == null)
- return;
- if (propertyId == BR.config) {
- config.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
- breakObjectOrientedLayeringHandlerReceivers.add(config);
- config.getInterfaceSection().addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
- breakObjectOrientedLayeringHandlerReceivers.add(config.getInterfaceSection());
- config.getPeers().addOnListChangedCallback(breakObjectListOrientedLayeringHandler);
- breakObjectOrientedLayeringHandlerReceivers.add(config.getPeers());
- } else if (propertyId == BR.dnses || propertyId == BR.peers)
- ;
- else
- return;
- final int numSiblings = config.getPeers().size() - 1;
- for (final Peer.Observable peer : config.getPeers()) {
- peer.setInterfaceDNSRoutes(config.getInterfaceSection().getDnses());
- peer.setNumSiblings(numSiblings);
- }
- }
- };
- private final ObservableList.OnListChangedCallback<? extends ObservableList<Peer.Observable>> breakObjectListOrientedLayeringHandler = new ObservableList.OnListChangedCallback<ObservableList<Peer.Observable>>() {
- @Override
- public void onChanged(final ObservableList<Peer.Observable> sender) {
- }
-
- @Override
- public void onItemRangeChanged(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
- }
-
- @Override
- public void onItemRangeInserted(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
- if (binding != null)
- breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
- }
- @Override
- public void onItemRangeMoved(final ObservableList<Peer.Observable> sender, final int fromPosition, final int toPosition, final int itemCount) {
- }
-
- @Override
- public void onItemRangeRemoved(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
- if (binding != null)
- breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
- }
- };
+ @Nullable private TunnelEditorFragmentBinding binding;
@Nullable private Tunnel tunnel;
- private void onConfigLoaded(final String name, final Config config) {
+ private void onConfigLoaded(final Config config) {
if (binding != null) {
- binding.setConfig(new Config.Observable(config, name));
+ binding.setConfig(new ConfigProxy(config));
}
}
@@ -143,29 +88,23 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
@Nullable final Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
binding = TunnelEditorFragmentBinding.inflate(inflater, container, false);
- binding.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
- breakObjectOrientedLayeringHandlerReceivers.add(binding);
binding.executePendingBindings();
return binding.getRoot();
}
- @SuppressWarnings("unchecked")
@Override
public void onDestroyView() {
binding = null;
- for (final Object o : breakObjectOrientedLayeringHandlerReceivers) {
- if (o instanceof Observable)
- ((Observable) o).removeOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
- else if (o instanceof ObservableList)
- ((ObservableList) o).removeOnListChangedCallback(breakObjectListOrientedLayeringHandler);
- }
super.onDestroyView();
}
@Override
public void onExcludedAppsSelected(final List<String> excludedApps) {
Objects.requireNonNull(binding, "Tried to set excluded apps while no view was loaded");
- binding.getConfig().getInterfaceSection().setExcludedApplications(Attribute.iterableToString(excludedApps));
+ final ObservableList<String> excludedApplications =
+ binding.getConfig().getInterface().getExcludedApplications();
+ excludedApplications.clear();
+ excludedApplications.addAll(excludedApps);
}
private void onFinished() {
@@ -195,25 +134,27 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_action_save:
- final Config newConfig = new Config();
+ if (binding == null)
+ return false;
+ final Config newConfig;
try {
- binding.getConfig().commitData(newConfig);
+ newConfig = binding.getConfig().resolve();
} catch (final Exception e) {
final String error = ExceptionLoggers.unwrapMessage(e);
- final String tunnelName = tunnel == null ? binding.getConfig().getName() : tunnel.getName();
+ final String tunnelName = tunnel == null ? binding.getName() : tunnel.getName();
final String message = getString(R.string.config_save_error, tunnelName, error);
Log.e(TAG, message, e);
Snackbar.make(binding.mainContainer, error, Snackbar.LENGTH_LONG).show();
return false;
}
if (tunnel == null) {
- Log.d(TAG, "Attempting to create new tunnel " + binding.getConfig().getName());
+ Log.d(TAG, "Attempting to create new tunnel " + binding.getName());
final TunnelManager manager = Application.getTunnelManager();
- manager.create(binding.getConfig().getName(), newConfig)
+ manager.create(binding.getName(), newConfig)
.whenComplete(this::onTunnelCreated);
- } else if (!tunnel.getName().equals(binding.getConfig().getName())) {
- Log.d(TAG, "Attempting to rename tunnel to " + binding.getConfig().getName());
- tunnel.setName(binding.getConfig().getName())
+ } else if (!tunnel.getName().equals(binding.getName())) {
+ Log.d(TAG, "Attempting to rename tunnel to " + binding.getName());
+ tunnel.setName(binding.getName())
.whenComplete((a, b) -> onTunnelRenamed(tunnel, newConfig, b));
} else {
Log.d(TAG, "Attempting to save config of " + tunnel.getName());
@@ -229,7 +170,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) {
final FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null && binding != null) {
- final String[] excludedApps = Attribute.stringToList(binding.getConfig().getInterfaceSection().getExcludedApplications());
+ final ArrayList<String> excludedApps = new ArrayList<>(binding.getConfig().getInterface().getExcludedApplications());
final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this);
fragment.show(fragmentManager, null);
}
@@ -237,19 +178,25 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
@Override
public void onSaveInstanceState(final Bundle outState) {
- outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig());
+ if (binding != null)
+ outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig());
outState.putString(KEY_ORIGINAL_NAME, tunnel == null ? null : tunnel.getName());
super.onSaveInstanceState(outState);
}
@Override
- public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
+ public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel,
+ @Nullable final Tunnel newTunnel) {
tunnel = newTunnel;
if (binding == null)
return;
- binding.setConfig(new Config.Observable(null, null));
- if (tunnel != null)
- tunnel.getConfigAsync().thenAccept(a -> onConfigLoaded(tunnel.getName(), a));
+ binding.setConfig(new ConfigProxy());
+ if (tunnel != null) {
+ binding.setName(tunnel.getName());
+ tunnel.getConfigAsync().thenAccept(this::onConfigLoaded);
+ } else {
+ binding.setName("");
+ }
}
private void onTunnelCreated(final Tunnel newTunnel, @Nullable final Throwable throwable) {
@@ -301,7 +248,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
onSelectedTunnelChanged(null, getSelectedTunnel());
} else {
tunnel = getSelectedTunnel();
- final Config.Observable config = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG);
+ final ConfigProxy config = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG);
final String originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME);
if (tunnel != null && !tunnel.getName().equals(originalName))
onSelectedTunnelChanged(null, tunnel);
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
index 7509e40c..783c0a29 100644
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
+++ b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
@@ -41,9 +41,10 @@ import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.widget.MultiselectableRelativeLayout;
import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener;
import com.wireguard.config.Config;
+import com.wireguard.config.ParseException;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
@@ -79,13 +80,13 @@ public class TunnelListFragment extends BaseFragment {
private void importTunnel(@NonNull final String configText) {
try {
// Ensure the config text is parseable before proceeding…
- Config.from(configText);
+ Config.parse(new ByteArrayInputStream(configText.getBytes(StandardCharsets.UTF_8)));
// Config text is valid, now create the tunnel…
final FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null)
ConfigNamingDialogFragment.newInstance(configText).show(fragmentManager, null);
- } catch (final Exception exception) {
+ } catch (final IllegalArgumentException | IOException | ParseException exception) {
onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(exception));
}
}
@@ -122,7 +123,6 @@ public class TunnelListFragment extends BaseFragment {
if (isZip) {
try (ZipInputStream zip = new ZipInputStream(contentResolver.openInputStream(uri))) {
- BufferedReader reader = new BufferedReader(new InputStreamReader(zip, StandardCharsets.UTF_8));
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) {
if (entry.isDirectory())
@@ -140,7 +140,7 @@ public class TunnelListFragment extends BaseFragment {
continue;
Config config = null;
try {
- config = Config.from(reader);
+ config = Config.parse(zip);
} catch (Exception e) {
throwables.add(e);
}
@@ -150,7 +150,7 @@ public class TunnelListFragment extends BaseFragment {
}
} else {
futureTunnels.add(Application.getTunnelManager().create(name,
- Config.from(contentResolver.openInputStream(uri))).toCompletableFuture());
+ Config.parse(contentResolver.openInputStream(uri))).toCompletableFuture());
}
if (futureTunnels.isEmpty()) {
diff --git a/app/src/main/java/com/wireguard/android/model/ApplicationData.java b/app/src/main/java/com/wireguard/android/model/ApplicationData.java
index efe1ef87..f7c335de 100644
--- a/app/src/main/java/com/wireguard/android/model/ApplicationData.java
+++ b/app/src/main/java/com/wireguard/android/model/ApplicationData.java
@@ -13,7 +13,6 @@ import com.wireguard.android.BR;
import com.wireguard.util.Keyed;
public class ApplicationData extends BaseObservable implements Keyed<String> {
-
private final Drawable icon;
private final String name;
private final String packageName;
diff --git a/app/src/main/java/com/wireguard/android/model/Tunnel.java b/app/src/main/java/com/wireguard/android/model/Tunnel.java
index 6d37e009..9092b288 100644
--- a/app/src/main/java/com/wireguard/android/model/Tunnel.java
+++ b/app/src/main/java/com/wireguard/android/model/Tunnel.java
@@ -49,7 +49,8 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
return manager.delete(this);
}
- @Bindable @Nullable
+ @Bindable
+ @Nullable
public Config getConfig() {
if (config == null)
manager.getTunnelConfig(this).whenComplete(ExceptionLoggers.E);
@@ -81,7 +82,8 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
return TunnelManager.getTunnelState(this);
}
- @Bindable @Nullable
+ @Bindable
+ @Nullable
public Statistics getStatistics() {
// FIXME: Check age of statistics.
if (statistics == null)
diff --git a/app/src/main/java/com/wireguard/android/model/TunnelManager.java b/app/src/main/java/com/wireguard/android/model/TunnelManager.java
index 3fd7bfc0..83df3595 100644
--- a/app/src/main/java/com/wireguard/android/model/TunnelManager.java
+++ b/app/src/main/java/com/wireguard/android/model/TunnelManager.java
@@ -44,6 +44,7 @@ public final class TunnelManager extends BaseObservable {
private static final String KEY_LAST_USED_TUNNEL = "last_used_tunnel";
private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
+
private final CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> completableTunnels = new CompletableFuture<>();
private final ConfigStore configStore;
private final Context context = Application.get();
@@ -111,7 +112,8 @@ public final class TunnelManager extends BaseObservable {
});
}
- @Bindable @Nullable
+ @Bindable
+ @Nullable
public Tunnel getLastUsedTunnel() {
return lastUsedTunnel;
}
diff --git a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java b/app/src/main/java/com/wireguard/android/preference/VersionPreference.java
index 228facc7..1f3f5aa8 100644
--- a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java
+++ b/app/src/main/java/com/wireguard/android/preference/VersionPreference.java
@@ -34,7 +34,8 @@ public class VersionPreference extends Preference {
});
}
- @Override @Nullable
+ @Nullable
+ @Override
public CharSequence getSummary() {
return versionSummary;
}
diff --git a/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java b/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java
index a32e77a4..199b1fbd 100644
--- a/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java
+++ b/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java
@@ -5,9 +5,15 @@
package com.wireguard.android.util;
+import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.util.Log;
+import com.wireguard.android.Application;
+import com.wireguard.android.R;
+import com.wireguard.config.ParseException;
+import com.wireguard.crypto.Key;
+
import java9.util.concurrent.CompletionException;
import java9.util.function.BiConsumer;
@@ -34,12 +40,35 @@ public enum ExceptionLoggers implements BiConsumer<Object, Throwable> {
return throwable;
}
- public static String unwrapMessage(Throwable throwable) {
- throwable = unwrap(throwable);
- final String message = throwable.getMessage();
- if (message != null)
- return message;
- return throwable.getClass().getSimpleName();
+ public static String unwrapMessage(final Throwable throwable) {
+ final Throwable innerThrowable = unwrap(throwable);
+ final Resources resources = Application.get().getResources();
+ String message;
+ if (innerThrowable instanceof ParseException) {
+ final ParseException parseException = (ParseException) innerThrowable;
+ message = resources.getString(R.string.parse_error, parseException.getText(), parseException.getContext());
+ if (parseException.getMessage() != null)
+ message += ": " + parseException.getMessage();
+ } else if (innerThrowable instanceof Key.KeyFormatException) {
+ final Key.KeyFormatException keyFormatException = (Key.KeyFormatException) innerThrowable;
+ switch (keyFormatException.getFormat()) {
+ case BASE64:
+ message = resources.getString(R.string.key_length_base64_exception_message);
+ break;
+ case BINARY:
+ message = resources.getString(R.string.key_length_exception_message);
+ break;
+ case HEX:
+ message = resources.getString(R.string.key_length_hex_exception_message);
+ break;
+ default:
+ // Will never happen, as getFormat is not nullable.
+ message = null;
+ }
+ } else {
+ message = throwable.getMessage();
+ }
+ return message != null ? message : innerThrowable.getClass().getSimpleName();
}
@Override
diff --git a/app/src/main/java/com/wireguard/android/util/FragmentUtils.java b/app/src/main/java/com/wireguard/android/util/FragmentUtils.java
index d5838a95..b7fdd095 100644
--- a/app/src/main/java/com/wireguard/android/util/FragmentUtils.java
+++ b/app/src/main/java/com/wireguard/android/util/FragmentUtils.java
@@ -11,7 +11,6 @@ import android.view.ContextThemeWrapper;
import com.wireguard.android.activity.SettingsActivity;
public final class FragmentUtils {
-
private FragmentUtils() {
// Prevent instantiation
}
diff --git a/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java b/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java
index 2ba87535..7af829fb 100644
--- a/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java
+++ b/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java
@@ -64,13 +64,15 @@ public class ObservableKeyedArrayList<K, E extends Keyed<? extends K>>
return indexOfKey(key) >= 0;
}
- @Override @Nullable
+ @Nullable
+ @Override
public E get(final K key) {
final int index = indexOfKey(key);
return index >= 0 ? get(index) : null;
}
- @Override @Nullable
+ @Nullable
+ @Override
public E getLast(final K key) {
final int index = lastIndexOfKey(key);
return index >= 0 ? get(index) : null;
diff --git a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java b/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java
index 7ef94106..d287d33d 100644
--- a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java
+++ b/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java
@@ -28,8 +28,7 @@ import java.util.Spliterator;
public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
extends ObservableKeyedArrayList<K, E> implements ObservableSortedKeyedList<K, E> {
- @Nullable
- private final Comparator<? super K> comparator;
+ @Nullable private final Comparator<? super K> comparator;
private final transient KeyList<K, E> keyList = new KeyList<>(this);
@SuppressWarnings("WeakerAccess")
diff --git a/app/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java b/app/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java
new file mode 100644
index 00000000..abe8cbcf
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright © 2017-2018 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.viewmodel;
+
+import android.databinding.ObservableArrayList;
+import android.databinding.ObservableList;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.wireguard.config.Config;
+import com.wireguard.config.ParseException;
+import com.wireguard.config.Peer;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class ConfigProxy implements Parcelable {
+ public static final Parcelable.Creator<ConfigProxy> CREATOR = new ConfigProxyCreator();
+
+ private final InterfaceProxy interfaze;
+ private final ObservableList<PeerProxy> peers = new ObservableArrayList<>();
+
+ private ConfigProxy(final Parcel in) {
+ interfaze = in.readParcelable(InterfaceProxy.class.getClassLoader());
+ in.readTypedList(peers, PeerProxy.CREATOR);
+ for (final PeerProxy proxy : peers)
+ proxy.bind(this);
+ }
+
+ public ConfigProxy(final Config other) {
+ interfaze = new InterfaceProxy(other.getInterface());
+ for (final Peer peer : other.getPeers()) {
+ final PeerProxy proxy = new PeerProxy(peer);
+ peers.add(proxy);
+ proxy.bind(this);
+ }
+ }
+
+ public ConfigProxy() {
+ interfaze = new InterfaceProxy();
+ }
+
+ public PeerProxy addPeer() {
+ final PeerProxy proxy = new PeerProxy();
+ peers.add(proxy);
+ proxy.bind(this);
+ return proxy;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public InterfaceProxy getInterface() {
+ return interfaze;
+ }
+
+ public ObservableList<PeerProxy> getPeers() {
+ return peers;
+ }
+
+ public Config resolve() throws ParseException {
+ final Collection<Peer> resolvedPeers = new ArrayList<>();
+ for (final PeerProxy proxy : peers)
+ resolvedPeers.add(proxy.resolve());
+ return new Config.Builder()
+ .setInterface(interfaze.resolve())
+ .addPeers(resolvedPeers)
+ .build();
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeParcelable(interfaze, flags);
+ dest.writeTypedList(peers);
+ }
+
+ private static class ConfigProxyCreator implements Parcelable.Creator<ConfigProxy> {
+ @Override
+ public ConfigProxy createFromParcel(final Parcel in) {
+ return new ConfigProxy(in);
+ }
+
+ @Override
+ public ConfigProxy[] newArray(final int size) {
+ return new ConfigProxy[size];
+ }
+ }
+}
diff --git a/app/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java b/app/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java
new file mode 100644
index 00000000..63d82042
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright © 2017-2018 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.viewmodel;
+
+import android.databinding.BaseObservable;
+import android.databinding.Bindable;
+import android.databinding.ObservableArrayList;
+import android.databinding.ObservableList;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.wireguard.android.BR;
+import com.wireguard.config.Attribute;
+import com.wireguard.config.Interface;
+import com.wireguard.config.ParseException;
+import com.wireguard.crypto.Key;
+import com.wireguard.crypto.KeyPair;
+
+import java.net.InetAddress;
+import java.util.List;
+
+import java9.util.stream.Collectors;
+import java9.util.stream.StreamSupport;
+
+public class InterfaceProxy extends BaseObservable implements Parcelable {
+ public static final Parcelable.Creator<InterfaceProxy> CREATOR = new InterfaceProxyCreator();
+
+ private final ObservableList<String> excludedApplications = new ObservableArrayList<>();
+ private String addresses;
+ private String dnsServers;
+ private String listenPort;
+ private String mtu;
+ private String privateKey;
+ private String publicKey;
+
+ private InterfaceProxy(final Parcel in) {
+ addresses = in.readString();
+ dnsServers = in.readString();
+ in.readStringList(excludedApplications);
+ listenPort = in.readString();
+ mtu = in.readString();
+ privateKey = in.readString();
+ publicKey = in.readString();
+ }
+
+ public InterfaceProxy(final Interface other) {
+ addresses = Attribute.join(other.getAddresses());
+ final List<String> dnsServerStrings = StreamSupport.stream(other.getDnsServers())
+ .map(InetAddress::getHostAddress)
+ .collect(Collectors.toUnmodifiableList());
+ dnsServers = Attribute.join(dnsServerStrings);
+ excludedApplications.addAll(other.getExcludedApplications());
+ listenPort = other.getListenPort().map(String::valueOf).orElse("");
+ mtu = other.getMtu().map(String::valueOf).orElse("");
+ final KeyPair keyPair = other.getKeyPair();
+ privateKey = keyPair.getPrivateKey().toBase64();
+ publicKey = keyPair.getPublicKey().toBase64();
+ }
+
+ public InterfaceProxy() {
+ addresses = "";
+ dnsServers = "";
+ listenPort = "";
+ mtu = "";
+ privateKey = "";
+ publicKey = "";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public void generateKeyPair() {
+ final KeyPair keyPair = new KeyPair();
+ privateKey = keyPair.getPrivateKey().toBase64();
+ publicKey = keyPair.getPublicKey().toBase64();
+ notifyPropertyChanged(BR.privateKey);
+ notifyPropertyChanged(BR.publicKey);
+ }
+
+ @Bindable
+ public String getAddresses() {
+ return addresses;
+ }
+
+ @Bindable
+ public String getDnsServers() {
+ return dnsServers;
+ }
+
+ public ObservableList<String> getExcludedApplications() {
+ return excludedApplications;
+ }
+
+ @Bindable
+ public String getListenPort() {
+ return listenPort;
+ }
+
+ @Bindable
+ public String getMtu() {
+ return mtu;
+ }
+
+ @Bindable
+ public String getPrivateKey() {
+ return privateKey;
+ }
+
+ @Bindable
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ public Interface resolve() throws ParseException {
+ final Interface.Builder builder = new Interface.Builder();
+ if (!addresses.isEmpty())
+ builder.parseAddresses(addresses);
+ if (!dnsServers.isEmpty())
+ builder.parseDnsServers(dnsServers);
+ if (!excludedApplications.isEmpty())
+ builder.excludeApplications(excludedApplications);
+ if (!listenPort.isEmpty())
+ builder.parseListenPort(listenPort);
+ if (!mtu.isEmpty())
+ builder.parseMtu(mtu);
+ if (!privateKey.isEmpty())
+ builder.parsePrivateKey(privateKey);
+ return builder.build();
+ }
+
+ public void setAddresses(final String addresses) {
+ this.addresses = addresses;
+ notifyPropertyChanged(BR.addresses);
+ }
+
+ public void setDnsServers(final String dnsServers) {
+ this.dnsServers = dnsServers;
+ notifyPropertyChanged(BR.dnsServers);
+ }
+
+ public void setListenPort(final String listenPort) {
+ this.listenPort = listenPort;
+ notifyPropertyChanged(BR.listenPort);
+ }
+
+ public void setMtu(final String mtu) {
+ this.mtu = mtu;
+ notifyPropertyChanged(BR.mtu);
+ }
+
+ public void setPrivateKey(final String privateKey) {
+ this.privateKey = privateKey;
+ try {
+ publicKey = new KeyPair(Key.fromBase64(privateKey)).getPublicKey().toBase64();
+ } catch (final Key.KeyFormatException ignored) {
+ publicKey = "";
+ }
+ notifyPropertyChanged(BR.privateKey);
+ notifyPropertyChanged(BR.publicKey);
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeString(addresses);
+ dest.writeString(dnsServers);
+ dest.writeStringList(excludedApplications);
+ dest.writeString(listenPort);
+ dest.writeString(mtu);
+ dest.writeString(privateKey);
+ dest.writeString(publicKey);
+ }
+
+ private static class InterfaceProxyCreator implements Parcelable.Creator<InterfaceProxy> {
+ @Override
+ public InterfaceProxy createFromParcel(final Parcel in) {
+ return new InterfaceProxy(in);
+ }
+
+ @Override
+ public InterfaceProxy[] newArray(final int size) {
+ return new InterfaceProxy[size];
+ }
+ }
+}
diff --git a/app/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java b/app/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java
new file mode 100644
index 00000000..822a4278
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright © 2017-2018 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.viewmodel;
+
+import android.databinding.BaseObservable;
+import android.databinding.Bindable;
+import android.databinding.Observable;
+import android.databinding.ObservableList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+
+import com.wireguard.android.BR;
+import com.wireguard.config.Attribute;
+import com.wireguard.config.InetEndpoint;
+import com.wireguard.config.ParseException;
+import com.wireguard.config.Peer;
+import com.wireguard.crypto.Key;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import java9.util.Lists;
+import java9.util.Sets;
+import java9.util.stream.Collectors;
+import java9.util.stream.Stream;
+
+public class PeerProxy extends BaseObservable implements Parcelable {
+ public static final Parcelable.Creator<PeerProxy> CREATOR = new PeerProxyCreator();
+ private static final Set<String> IPV4_PUBLIC_NETWORKS = new LinkedHashSet<>(Lists.of(
+ "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
+ "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
+ "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
+ "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
+ "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
+ "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
+ ));
+ private static final Set<String> IPV4_WILDCARD = Sets.of("0.0.0.0/0");
+
+ private final List<String> dnsRoutes = new ArrayList<>();
+ private String allowedIps;
+ private AllowedIpsState allowedIpsState = AllowedIpsState.INVALID;
+ private String endpoint;
+ @Nullable private InterfaceDnsListener interfaceDnsListener;
+ @Nullable private ConfigProxy owner;
+ @Nullable private PeerListListener peerListListener;
+ private String persistentKeepalive;
+ private String preSharedKey;
+ private String publicKey;
+ private int totalPeers;
+
+ private PeerProxy(final Parcel in) {
+ allowedIps = in.readString();
+ endpoint = in.readString();
+ persistentKeepalive = in.readString();
+ preSharedKey = in.readString();
+ publicKey = in.readString();
+ }
+
+ public PeerProxy(final Peer other) {
+ allowedIps = Attribute.join(other.getAllowedIps());
+ endpoint = other.getEndpoint().map(InetEndpoint::toString).orElse("");
+ persistentKeepalive = other.getPersistentKeepalive().map(String::valueOf).orElse("");
+ preSharedKey = other.getPreSharedKey().map(Key::toBase64).orElse("");
+ publicKey = other.getPublicKey().toBase64();
+ }
+
+ public PeerProxy() {
+ allowedIps = "";
+ endpoint = "";
+ persistentKeepalive = "";
+ preSharedKey = "";
+ publicKey = "";
+ }
+
+ public void bind(final ConfigProxy owner) {
+ final InterfaceProxy interfaze = owner.getInterface();
+ final ObservableList<PeerProxy> peers = owner.getPeers();
+ if (interfaceDnsListener == null)
+ interfaceDnsListener = new InterfaceDnsListener(this);
+ interfaze.addOnPropertyChangedCallback(interfaceDnsListener);
+ setInterfaceDns(interfaze.getDnsServers());
+ if (peerListListener == null)
+ peerListListener = new PeerListListener(this);
+ peers.addOnListChangedCallback(peerListListener);
+ setTotalPeers(peers.size());
+ this.owner = owner;
+ }
+
+ private void calculateAllowedIpsState() {
+ final AllowedIpsState newState;
+ if (totalPeers == 1) {
+ // String comparison works because we only care if allowedIps is a superset of one of
+ // the above sets of (valid) *networks*. We are not checking for a superset based on
+ // the individual addresses in each set.
+ final Collection<String> networkStrings = getAllowedIpsSet();
+ // If allowedIps contains both the wildcard and the public networks, then private
+ // networks aren't excluded!
+ if (networkStrings.containsAll(IPV4_WILDCARD))
+ newState = AllowedIpsState.CONTAINS_IPV4_WILDCARD;
+ else if (networkStrings.containsAll(IPV4_PUBLIC_NETWORKS))
+ newState = AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS;
+ else
+ newState = AllowedIpsState.OTHER;
+ } else {
+ newState = AllowedIpsState.INVALID;
+ }
+ if (newState != allowedIpsState) {
+ allowedIpsState = newState;
+ notifyPropertyChanged(BR.ableToExcludePrivateIps);
+ notifyPropertyChanged(BR.excludingPrivateIps);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Bindable
+ public String getAllowedIps() {
+ return allowedIps;
+ }
+
+ private Set<String> getAllowedIpsSet() {
+ return new LinkedHashSet<>(Lists.of(Attribute.split(allowedIps)));
+ }
+
+ @Bindable
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+ @Bindable
+ public String getPersistentKeepalive() {
+ return persistentKeepalive;
+ }
+
+ @Bindable
+ public String getPreSharedKey() {
+ return preSharedKey;
+ }
+
+ @Bindable
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ @Bindable
+ public boolean isAbleToExcludePrivateIps() {
+ return allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS
+ || allowedIpsState == AllowedIpsState.CONTAINS_IPV4_WILDCARD;
+ }
+
+ @Bindable
+ public boolean isExcludingPrivateIps() {
+ return allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS;
+ }
+
+ public Peer resolve() throws ParseException {
+ final Peer.Builder builder = new Peer.Builder();
+ if (!allowedIps.isEmpty())
+ builder.parseAllowedIPs(allowedIps);
+ if (!endpoint.isEmpty())
+ builder.parseEndpoint(endpoint);
+ if (!persistentKeepalive.isEmpty())
+ builder.parsePersistentKeepalive(persistentKeepalive);
+ if (!preSharedKey.isEmpty())
+ builder.parsePreSharedKey(preSharedKey);
+ if (!publicKey.isEmpty())
+ builder.parsePublicKey(publicKey);
+ return builder.build();
+ }
+
+ public void setAllowedIps(final String allowedIps) {
+ this.allowedIps = allowedIps;
+ notifyPropertyChanged(BR.allowedIps);
+ calculateAllowedIpsState();
+ }
+
+ public void setEndpoint(final String endpoint) {
+ this.endpoint = endpoint;
+ notifyPropertyChanged(BR.endpoint);
+ }
+
+ public void setExcludingPrivateIps(final boolean excludingPrivateIps) {
+ if (!isAbleToExcludePrivateIps() || isExcludingPrivateIps() == excludingPrivateIps)
+ return;
+ final Set<String> oldNetworks = excludingPrivateIps ? IPV4_WILDCARD : IPV4_PUBLIC_NETWORKS;
+ final Set<String> newNetworks = excludingPrivateIps ? IPV4_PUBLIC_NETWORKS : IPV4_WILDCARD;
+ final Collection<String> input = getAllowedIpsSet();
+ final int outputSize = input.size() - oldNetworks.size() + newNetworks.size();
+ final Collection<String> output = new LinkedHashSet<>(outputSize);
+ boolean replaced = false;
+ // Replace the first instance of the wildcard with the public network list, or vice versa.
+ for (final String network : input) {
+ if (oldNetworks.contains(network)) {
+ if (!replaced) {
+ for (final String replacement : newNetworks)
+ if (!output.contains(replacement))
+ output.add(replacement);
+ replaced = true;
+ }
+ } else if (!output.contains(network)) {
+ output.add(network);
+ }
+ }
+ // DNS servers only need to handled specially when we're excluding private IPs.
+ if (excludingPrivateIps)
+ output.addAll(dnsRoutes);
+ else
+ output.removeAll(dnsRoutes);
+ allowedIps = Attribute.join(output);
+ allowedIpsState = excludingPrivateIps ?
+ AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS : AllowedIpsState.CONTAINS_IPV4_WILDCARD;
+ notifyPropertyChanged(BR.allowedIps);
+ notifyPropertyChanged(BR.excludingPrivateIps);
+ }
+
+ private void setInterfaceDns(final CharSequence dnsServers) {
+ final List<String> newDnsRoutes = Stream.of(Attribute.split(dnsServers))
+ .map(server -> server + "/32")
+ .collect(Collectors.toUnmodifiableList());
+ if (allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS) {
+ final Collection<String> input = getAllowedIpsSet();
+ final Collection<String> output = new LinkedHashSet<>(input.size() + 1);
+ // Yes, this is quadratic in the number of DNS servers, but most users have 1 or 2.
+ for (final String network : input)
+ if (!dnsRoutes.contains(network) || newDnsRoutes.contains(network))
+ output.add(network);
+ // Since output is a Set, this does the Right Thing™ (it does not duplicate networks).
+ output.addAll(newDnsRoutes);
+ // None of the public networks are /32s, so this cannot change the AllowedIPs state.
+ allowedIps = Attribute.join(output);
+ notifyPropertyChanged(BR.allowedIps);
+ }
+ dnsRoutes.clear();
+ dnsRoutes.addAll(newDnsRoutes);
+ }
+
+ public void setPersistentKeepalive(final String persistentKeepalive) {
+ this.persistentKeepalive = persistentKeepalive;
+ notifyPropertyChanged(BR.persistentKeepalive);
+ }
+
+ public void setPreSharedKey(final String preSharedKey) {
+ this.preSharedKey = preSharedKey;
+ notifyPropertyChanged(BR.preSharedKey);
+ }
+
+ public void setPublicKey(final String publicKey) {
+ this.publicKey = publicKey;
+ notifyPropertyChanged(BR.publicKey);
+ }
+
+ private void setTotalPeers(final int totalPeers) {
+ if (this.totalPeers == totalPeers)
+ return;
+ this.totalPeers = totalPeers;
+ calculateAllowedIpsState();
+ }
+
+ public void unbind() {
+ if (owner == null)
+ return;
+ final InterfaceProxy interfaze = owner.getInterface();
+ final ObservableList<PeerProxy> peers = owner.getPeers();
+ if (interfaceDnsListener != null)
+ interfaze.removeOnPropertyChangedCallback(interfaceDnsListener);
+ if (peerListListener != null)
+ peers.removeOnListChangedCallback(peerListListener);
+ peers.remove(this);
+ setInterfaceDns("");
+ setTotalPeers(0);
+ owner = null;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeString(allowedIps);
+ dest.writeString(endpoint);
+ dest.writeString(persistentKeepalive);
+ dest.writeString(preSharedKey);
+ dest.writeString(publicKey);
+ }
+
+ private enum AllowedIpsState {
+ CONTAINS_IPV4_PUBLIC_NETWORKS,
+ CONTAINS_IPV4_WILDCARD,
+ INVALID,
+ OTHER
+ }
+
+ private static final class InterfaceDnsListener extends Observable.OnPropertyChangedCallback {
+ private final WeakReference<PeerProxy> weakPeerProxy;
+
+ private InterfaceDnsListener(final PeerProxy peerProxy) {
+ weakPeerProxy = new WeakReference<>(peerProxy);
+ }
+
+ @Override
+ public void onPropertyChanged(final Observable sender, final int propertyId) {
+ @Nullable final PeerProxy peerProxy = weakPeerProxy.get();
+ if (peerProxy == null) {
+ sender.removeOnPropertyChangedCallback(this);
+ return;
+ }
+ // This shouldn't be possible, but try to avoid a ClassCastException anyway.
+ if (!(sender instanceof InterfaceProxy))
+ return;
+ if (!(propertyId == BR._all || propertyId == BR.dnsServers))
+ return;
+ peerProxy.setInterfaceDns(((InterfaceProxy) sender).getDnsServers());
+ }
+ }
+
+ private static final class PeerListListener
+ extends ObservableList.OnListChangedCallback<ObservableList<PeerProxy>> {
+ private final WeakReference<PeerProxy> weakPeerProxy;
+
+ private PeerListListener(final PeerProxy peerProxy) {
+ weakPeerProxy = new WeakReference<>(peerProxy);
+ }
+
+ @Override
+ public void onChanged(final ObservableList<PeerProxy> sender) {
+ @Nullable final PeerProxy peerProxy = weakPeerProxy.get();
+ if (peerProxy == null) {
+ sender.removeOnListChangedCallback(this);
+ return;
+ }
+ peerProxy.setTotalPeers(sender.size());
+ }
+
+ @Override
+ public void onItemRangeChanged(final ObservableList<PeerProxy> sender,
+ final int positionStart, final int itemCount) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onItemRangeInserted(final ObservableList<PeerProxy> sender,
+ final int positionStart, final int itemCount) {
+ onChanged(sender);
+ }
+
+ @Override
+ public void onItemRangeMoved(final ObservableList<PeerProxy> sender,
+ final int fromPosition, final int toPosition,
+ final int itemCount) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onItemRangeRemoved(final ObservableList<PeerProxy> sender,
+ final int positionStart, final int itemCount) {
+ onChanged(sender);
+ }
+ }
+
+ private static class PeerProxyCreator implements Parcelable.Creator<PeerProxy> {
+ @Override
+ public PeerProxy createFromParcel(final Parcel in) {
+ return new PeerProxy(in);
+ }
+
+ @Override
+ public PeerProxy[] newArray(final int size) {
+ return new PeerProxy[size];
+ }
+ }
+}
diff --git a/app/src/main/java/com/wireguard/android/widget/KeyInputFilter.java b/app/src/main/java/com/wireguard/android/widget/KeyInputFilter.java
index b6cdada7..6332b856 100644
--- a/app/src/main/java/com/wireguard/android/widget/KeyInputFilter.java
+++ b/app/src/main/java/com/wireguard/android/widget/KeyInputFilter.java
@@ -10,7 +10,7 @@ import android.text.InputFilter;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
-import com.wireguard.crypto.KeyEncoding;
+import com.wireguard.crypto.Key;
/**
* InputFilter for entering WireGuard private/public keys encoded with base64.
@@ -25,7 +25,8 @@ public class KeyInputFilter implements InputFilter {
return new KeyInputFilter();
}
- @Override @Nullable
+ @Nullable
+ @Override
public CharSequence filter(final CharSequence source,
final int sStart, final int sEnd,
final Spanned dest,
@@ -38,9 +39,9 @@ public class KeyInputFilter implements InputFilter {
final int dIndex = dStart + (sIndex - sStart);
// Restrict characters to the base64 character set.
// Ensure adding this character does not push the length over the limit.
- if (((dIndex + 1 < KeyEncoding.KEY_LENGTH_BASE64 && isAllowed(c)) ||
- (dIndex + 1 == KeyEncoding.KEY_LENGTH_BASE64 && c == '=')) &&
- dLength + (sIndex - sStart) < KeyEncoding.KEY_LENGTH_BASE64) {
+ if (((dIndex + 1 < Key.Format.BASE64.getLength() && isAllowed(c)) ||
+ (dIndex + 1 == Key.Format.BASE64.getLength() && c == '=')) &&
+ dLength + (sIndex - sStart) < Key.Format.BASE64.getLength()) {
++rIndex;
} else {
if (replacement == null)
diff --git a/app/src/main/java/com/wireguard/android/widget/NameInputFilter.java b/app/src/main/java/com/wireguard/android/widget/NameInputFilter.java
index db5336d0..2352630e 100644
--- a/app/src/main/java/com/wireguard/android/widget/NameInputFilter.java
+++ b/app/src/main/java/com/wireguard/android/widget/NameInputFilter.java
@@ -25,7 +25,8 @@ public class NameInputFilter implements InputFilter {
return new NameInputFilter();
}
- @Override @Nullable
+ @Nullable
+ @Override
public CharSequence filter(final CharSequence source,
final int sStart, final int sEnd,
final Spanned dest,
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java
index ed838914..7f5b67e6 100644
--- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java
+++ b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java
@@ -539,7 +539,7 @@ public class FloatingActionsMenu extends ViewGroup {
return new SavedState[size];
}
};
- public boolean mExpanded;
+ private boolean mExpanded;
public SavedState(final Parcelable parcel) {
super(parcel);