Use validation instead of two-way binding

This is insane, but it appears to be working. We essentially store
things in a separate class for editing, and then commit it back at a
given time.

This business with onViewStateRestored in both TunnelEditorFragment and
in TunnelDetailFragment is buggy and likely wrong.

In general TunnelEditorFragment should probably be rewritten. The
relationship with the changed name is not clear.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2018-04-28 04:45:17 +02:00
parent 693228985d
commit 63071f57b7
11 changed files with 382 additions and 147 deletions

View File

@ -10,6 +10,7 @@ import android.view.ViewGroup;
import com.wireguard.android.R; import com.wireguard.android.R;
import com.wireguard.android.databinding.TunnelDetailFragmentBinding; import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.Tunnel;
import com.wireguard.config.Config;
/** /**
* Fragment that shows details about a specific tunnel. * Fragment that shows details about a specific tunnel.
@ -18,6 +19,7 @@ import com.wireguard.android.model.Tunnel;
public class TunnelDetailFragment extends BaseFragment { public class TunnelDetailFragment extends BaseFragment {
private TunnelDetailFragmentBinding binding; private TunnelDetailFragmentBinding binding;
private boolean isViewStateRestored; private boolean isViewStateRestored;
private String originalName;
@Override @Override
public void onCreate(final Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
@ -45,16 +47,34 @@ public class TunnelDetailFragment extends BaseFragment {
super.onDestroyView(); super.onDestroyView();
} }
private void onConfigLoaded(Config config) {
binding.setConfig(new Config.Observable(config, originalName));
}
@Override @Override
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) { public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
if (binding != null && isViewStateRestored) if (binding != null && isViewStateRestored) {
binding.setTunnel(newTunnel); binding.setTunnel(newTunnel);
if (newTunnel == null)
binding.setConfig(null);
else {
originalName = newTunnel.getName();
newTunnel.getConfigAsync().thenAccept(this::onConfigLoaded);
}
}
} }
@Override @Override
public void onViewStateRestored(final Bundle savedInstanceState) { public void onViewStateRestored(final Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState); super.onViewStateRestored(savedInstanceState);
binding.setTunnel(getSelectedTunnel()); Tunnel tunnel = getSelectedTunnel();
binding.setTunnel(tunnel);
if (tunnel == null)
binding.setConfig(null);
else {
originalName = tunnel.getName();
tunnel.getConfigAsync().thenAccept(this::onConfigLoaded);
}
isViewStateRestored = true; isViewStateRestored = true;
} }
} }

View File

@ -2,7 +2,6 @@ package com.wireguard.android.fragment;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.databinding.ObservableField;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
@ -31,11 +30,9 @@ import com.wireguard.config.Config;
public class TunnelEditorFragment extends BaseFragment { public class TunnelEditorFragment extends BaseFragment {
private static final String KEY_LOCAL_CONFIG = "local_config"; private static final String KEY_LOCAL_CONFIG = "local_config";
private static final String KEY_LOCAL_NAME = "local_name";
private static final String KEY_ORIGINAL_NAME = "original_name"; private static final String KEY_ORIGINAL_NAME = "original_name";
private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName(); private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
private final ObservableField<String> localName = new ObservableField<>("");
private TunnelEditorFragmentBinding binding; private TunnelEditorFragmentBinding binding;
private boolean isViewStateRestored; private boolean isViewStateRestored;
private Config localConfig = new Config(); private Config localConfig = new Config();
@ -56,7 +53,7 @@ public class TunnelEditorFragment extends BaseFragment {
private void onConfigLoaded(final Config config) { private void onConfigLoaded(final Config config) {
localConfig = copyParcelable(config); localConfig = copyParcelable(config);
if (binding != null && isViewStateRestored) if (binding != null && isViewStateRestored)
binding.setConfig(localConfig); binding.setConfig(new Config.Observable(localConfig, originalName));
} }
private void onConfigSaved(@SuppressWarnings("unused") final Config config, private void onConfigSaved(@SuppressWarnings("unused") final Config config,
@ -82,20 +79,17 @@ public class TunnelEditorFragment extends BaseFragment {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState != null) { if (savedInstanceState != null) {
localConfig = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG); localConfig = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG);
localName.set(savedInstanceState.getString(KEY_LOCAL_NAME));
originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME); originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME);
} }
// Erase the remains of creating or editing a different tunnel. // Erase the remains of creating or editing a different tunnel.
if (getSelectedTunnel() != null && !getSelectedTunnel().getName().equals(originalName)) { if (getSelectedTunnel() != null && !getSelectedTunnel().getName().equals(originalName)) {
// The config must be loaded asynchronously since it's not an observable property. // The config must be loaded asynchronously since it's not an observable property.
localConfig = null; localConfig = null;
getSelectedTunnel().getConfigAsync().thenAccept(this::onConfigLoaded);
originalName = getSelectedTunnel().getName(); originalName = getSelectedTunnel().getName();
localName.set(originalName); getSelectedTunnel().getConfigAsync().thenAccept(this::onConfigLoaded);
} else if (getSelectedTunnel() == null && originalName != null) { } else if (getSelectedTunnel() == null && originalName != null) {
localConfig = new Config(); localConfig = new Config();
originalName = null; originalName = null;
localName.set("");
} }
localTunnel = getSelectedTunnel(); localTunnel = getSelectedTunnel();
setHasOptionsMenu(true); setHasOptionsMenu(true);
@ -147,14 +141,26 @@ public class TunnelEditorFragment extends BaseFragment {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_action_save: case R.id.menu_action_save:
final Tunnel selectedTunnel = getSelectedTunnel(); final Tunnel selectedTunnel = getSelectedTunnel();
if (localConfig != null) {
try {
binding.getConfig().commitData(localConfig);
} catch (Exception e) {
final String error = ExceptionLoggers.unwrap(e).getMessage();
final String message = getString(R.string.config_save_error, localTunnel.getName(), error);
Log.e(TAG, message, e);
final CoordinatorLayout container = binding.mainContainer;
Snackbar.make(container, error, Snackbar.LENGTH_LONG).show();
return false;
}
}
if (selectedTunnel == null) { if (selectedTunnel == null) {
Log.d(TAG, "Attempting to create new tunnel " + localName.get()); Log.d(TAG, "Attempting to create new tunnel " + binding.getConfig().getName());
final TunnelManager manager = Application.getComponent().getTunnelManager(); final TunnelManager manager = Application.getComponent().getTunnelManager();
manager.create(localName.get(), localConfig) manager.create(binding.getConfig().getName(), localConfig)
.whenComplete(this::onTunnelCreated); .whenComplete(this::onTunnelCreated);
} else if (!selectedTunnel.getName().equals(localName.get())) { } else if (!selectedTunnel.getName().equals(binding.getConfig().getName())) {
Log.d(TAG, "Attempting to rename tunnel to " + localName.get()); Log.d(TAG, "Attempting to rename tunnel to " + binding.getConfig().getName());
selectedTunnel.setName(localName.get()) selectedTunnel.setName(binding.getConfig().getName())
.whenComplete(this::onTunnelRenamed); .whenComplete(this::onTunnelRenamed);
} else if (localConfig != null) { } else if (localConfig != null) {
Log.d(TAG, "Attempting to save config of " + selectedTunnel.getName()); Log.d(TAG, "Attempting to save config of " + selectedTunnel.getName());
@ -170,7 +176,6 @@ public class TunnelEditorFragment extends BaseFragment {
@Override @Override
public void onSaveInstanceState(final Bundle outState) { public void onSaveInstanceState(final Bundle outState) {
outState.putParcelable(KEY_LOCAL_CONFIG, localConfig); outState.putParcelable(KEY_LOCAL_CONFIG, localConfig);
outState.putString(KEY_LOCAL_NAME, localName.get());
outState.putString(KEY_ORIGINAL_NAME, originalName); outState.putString(KEY_ORIGINAL_NAME, originalName);
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
} }
@ -181,15 +186,13 @@ public class TunnelEditorFragment extends BaseFragment {
if (newTunnel != null) { if (newTunnel != null) {
// The config must be loaded asynchronously since it's not an observable property. // The config must be loaded asynchronously since it's not an observable property.
localConfig = null; localConfig = null;
newTunnel.getConfigAsync().thenAccept(this::onConfigLoaded);
originalName = newTunnel.getName(); originalName = newTunnel.getName();
localName.set(originalName); newTunnel.getConfigAsync().thenAccept(this::onConfigLoaded);
} else { } else {
localConfig = new Config(); localConfig = new Config();
if (binding != null && isViewStateRestored) if (binding != null && isViewStateRestored)
binding.setConfig(localConfig); binding.setConfig(new Config.Observable(localConfig, ""));
originalName = null; originalName = null;
localName.set("");
} }
localTunnel = newTunnel; localTunnel = newTunnel;
} }
@ -234,8 +237,9 @@ public class TunnelEditorFragment extends BaseFragment {
@Override @Override
public void onViewStateRestored(final Bundle savedInstanceState) { public void onViewStateRestored(final Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState); super.onViewStateRestored(savedInstanceState);
binding.setConfig(localConfig); if (localConfig == null)
binding.setName(localName); localConfig = new Config();
binding.setConfig(new Config.Observable(localConfig, originalName));
isViewStateRestored = true; isViewStateRestored = true;
} }
} }

View File

@ -54,6 +54,8 @@ enum Attribute {
} }
public static String[] stringToList(final String string) { public static String[] stringToList(final String string) {
if (string == null)
return new String[0];
return string.trim().split("\\s*,\\s*"); return string.trim().split("\\s*,\\s*");
} }

View File

@ -1,6 +1,9 @@
package com.wireguard.config; package com.wireguard.config;
import com.android.databinding.library.baseAdapters.BR;
import android.databinding.BaseObservable; import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.databinding.ObservableArrayList; import android.databinding.ObservableArrayList;
import android.databinding.ObservableList; import android.databinding.ObservableList;
import android.os.Parcel; import android.os.Parcel;
@ -11,12 +14,14 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/** /**
* Represents a wg-quick configuration file, its name, and its connection state. * Represents a wg-quick configuration file, its name, and its connection state.
*/ */
public class Config extends BaseObservable implements Parcelable { public class Config implements Parcelable {
public static final Creator<Config> CREATOR = new Creator<Config>() { public static final Creator<Config> CREATOR = new Creator<Config>() {
@Override @Override
public Config createFromParcel(final Parcel in) { public Config createFromParcel(final Parcel in) {
@ -29,8 +34,59 @@ public class Config extends BaseObservable implements Parcelable {
} }
}; };
public static class Observable extends BaseObservable {
private String name;
private Interface.Observable observableInterface;
private ObservableList<Peer.Observable> observablePeers;
public Observable(Config parent, String name) {
this.name = name;
loadData(parent);
}
public void loadData(Config parent) {
this.observableInterface = new Interface.Observable(parent.interfaceSection);
this.observablePeers = new ObservableArrayList<>();
for (Peer peer : parent.getPeers())
this.observablePeers.add(new Peer.Observable(peer));
}
public void commitData(Config parent) {
this.observableInterface.commitData(parent.interfaceSection);
List<Peer> newPeers = new ArrayList<>(this.observablePeers.size());
for (Peer.Observable observablePeer : this.observablePeers) {
Peer peer = new Peer();
observablePeer.commitData(peer);
newPeers.add(peer);
}
parent.peers = newPeers;
notifyChange();
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public Interface.Observable getInterfaceSection() {
return observableInterface;
}
@Bindable
public ObservableList<Peer.Observable> getPeers() {
return observablePeers;
}
}
private final Interface interfaceSection; private final Interface interfaceSection;
private final ObservableList<Peer> peers = new ObservableArrayList<>(); private List<Peer> peers = new ArrayList<>();
public Config() { public Config() {
interfaceSection = new Interface(); interfaceSection = new Interface();
@ -83,7 +139,7 @@ public class Config extends BaseObservable implements Parcelable {
return interfaceSection; return interfaceSection;
} }
public ObservableList<Peer> getPeers() { public List<Peer> getPeers() {
return peers; return peers;
} }

View File

@ -6,7 +6,6 @@ import android.os.Parcelable;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException;
public class IPCidr implements Parcelable { public class IPCidr implements Parcelable {
InetAddress address; InetAddress address;

View File

@ -10,7 +10,6 @@ import com.wireguard.crypto.KeyEncoding;
import com.wireguard.crypto.Keypair; import com.wireguard.crypto.Keypair;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -18,7 +17,7 @@ import java.util.List;
* Represents the configuration for a WireGuard interface (an [Interface] block). * Represents the configuration for a WireGuard interface (an [Interface] block).
*/ */
public class Interface extends BaseObservable implements Parcelable { public class Interface implements Parcelable {
public static final Creator<Interface> CREATOR = new Creator<Interface>() { public static final Creator<Interface> CREATOR = new Creator<Interface>() {
@Override @Override
public Interface createFromParcel(final Parcel in) { public Interface createFromParcel(final Parcel in) {
@ -31,12 +30,114 @@ public class Interface extends BaseObservable implements Parcelable {
} }
}; };
public static class Observable extends BaseObservable {
private String addresses;
private String dnses;
private String publicKey;
private String privateKey;
private String listenPort;
private String mtu;
public Observable(Interface parent) {
loadData(parent);
}
public void loadData(Interface parent) {
this.addresses = parent.getAddressString();
this.dnses = parent.getDnsString();
this.publicKey = parent.getPublicKey();
this.privateKey = parent.getPrivateKey();
this.listenPort = parent.getListenPortString();
this.mtu = parent.getMtuString();
}
public void commitData(Interface parent) {
parent.setAddressString(this.addresses);
parent.setDnsString(this.dnses);
parent.setPrivateKey(this.privateKey);
parent.setListenPortString(this.listenPort);
parent.setMtuString(this.mtu);
loadData(parent);
notifyChange();
}
@Bindable
public String getAddresses() {
return addresses;
}
public void setAddresses(String addresses) {
this.addresses = addresses;
notifyPropertyChanged(BR.addresses);
}
@Bindable
public String getDnses() {
return dnses;
}
public void setDnses(String dnses) {
this.dnses = dnses;
notifyPropertyChanged(BR.dnses);
}
@Bindable
public String getPublicKey() {
return publicKey;
}
@Bindable
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
try {
this.publicKey = new Keypair(privateKey).getPublicKey();
} catch (IllegalArgumentException ignored) {
this.publicKey = "";
}
notifyPropertyChanged(BR.privateKey);
notifyPropertyChanged(BR.publicKey);
}
public void generateKeypair() {
Keypair keypair = new Keypair();
privateKey = keypair.getPrivateKey();
publicKey = keypair.getPublicKey();
notifyPropertyChanged(BR.privateKey);
notifyPropertyChanged(BR.publicKey);
}
@Bindable
public String getListenPort() {
return listenPort;
}
public void setListenPort(String listenPort) {
this.listenPort = listenPort;
notifyPropertyChanged(BR.listenPort);
}
@Bindable
public String getMtu() {
return mtu;
}
public void setMtu(String mtu) {
this.mtu = mtu;
notifyPropertyChanged(BR.mtu);
}
}
private List<IPCidr> addressList; private List<IPCidr> addressList;
private List<InetAddress> dnsList; private List<InetAddress> dnsList;
private Keypair keypair; private Keypair keypair;
private int listenPort; private int listenPort;
private int mtu; private int mtu;
private String privateKey;
public Interface() { public Interface() {
addressList = new LinkedList<>(); addressList = new LinkedList<>();
@ -63,76 +164,63 @@ public class Interface extends BaseObservable implements Parcelable {
return 0; return 0;
} }
public void generateKeypair() { private String getAddressString() {
keypair = new Keypair();
privateKey = keypair.getPrivateKey();
notifyPropertyChanged(BR.privateKey);
notifyPropertyChanged(BR.publicKey);
}
@Bindable
public String getAddressString() {
if (addressList.isEmpty()) if (addressList.isEmpty())
return null; return null;
return Attribute.listToString(addressList); return Attribute.listToString(addressList);
} }
@Bindable
public IPCidr[] getAddresses() { public IPCidr[] getAddresses() {
return addressList.toArray(new IPCidr[addressList.size()]); return addressList.toArray(new IPCidr[addressList.size()]);
} }
public List<String> getDnsStrings() { private List<String> getDnsStrings() {
List<String> strings = new LinkedList<>(); List<String> strings = new LinkedList<>();
for (final InetAddress addr : dnsList) for (final InetAddress addr : dnsList)
strings.add(addr.getHostAddress()); strings.add(addr.getHostAddress());
return strings; return strings;
} }
@Bindable private String getDnsString() {
public String getDnsString() {
if (dnsList.isEmpty()) if (dnsList.isEmpty())
return null; return null;
return Attribute.listToString(getDnsStrings()); return Attribute.listToString(getDnsStrings());
} }
@Bindable
public InetAddress[] getDnses() { public InetAddress[] getDnses() {
return dnsList.toArray(new InetAddress[dnsList.size()]); return dnsList.toArray(new InetAddress[dnsList.size()]);
} }
@Bindable
public int getListenPort() { public int getListenPort() {
return listenPort; return listenPort;
} }
@Bindable private String getListenPortString() {
public String getListenPortString() {
if (listenPort == 0) if (listenPort == 0)
return null; return null;
return new Integer(listenPort).toString(); return new Integer(listenPort).toString();
} }
@Bindable
public int getMtu() { public int getMtu() {
return mtu; return mtu;
} }
@Bindable private String getMtuString() {
public String getMtuString() {
if (mtu == 0) if (mtu == 0)
return null; return null;
return new Integer(mtu).toString(); return new Integer(mtu).toString();
} }
@Bindable
public String getPrivateKey() { public String getPrivateKey() {
return privateKey; if (keypair == null)
return null;
return keypair.getPrivateKey();
} }
@Bindable
public String getPublicKey() { public String getPublicKey() {
return keypair != null ? keypair.getPublicKey() : null; if (keypair == null)
return null;
return keypair.getPublicKey();
} }
public void parse(final String line) { public void parse(final String line) {
@ -151,7 +239,7 @@ public class Interface extends BaseObservable implements Parcelable {
throw new IllegalArgumentException(line); throw new IllegalArgumentException(line);
} }
public void addAddresses(String[] addresses) { private void addAddresses(String[] addresses) {
if (addresses != null && addresses.length > 0) { if (addresses != null && addresses.length > 0) {
for (final String addr : addresses) { for (final String addr : addresses) {
if (addr.isEmpty()) if (addr.isEmpty())
@ -159,74 +247,55 @@ public class Interface extends BaseObservable implements Parcelable {
this.addressList.add(new IPCidr(addr)); this.addressList.add(new IPCidr(addr));
} }
} }
notifyPropertyChanged(BR.addresses);
notifyPropertyChanged(BR.addressString);
} }
public void setAddressString(final String addressString) { private void setAddressString(final String addressString) {
this.addressList.clear(); this.addressList.clear();
addAddresses(Attribute.stringToList(addressString)); addAddresses(Attribute.stringToList(addressString));
} }
public void addDnses(String[] dnses) { private void addDnses(String[] dnses) {
if (dnses != null && dnses.length > 0) { if (dnses != null && dnses.length > 0) {
for (final String dns : dnses) { for (final String dns : dnses) {
this.dnsList.add(Attribute.parseIPString(dns)); this.dnsList.add(Attribute.parseIPString(dns));
} }
} }
notifyPropertyChanged(BR.dnses);
notifyPropertyChanged(BR.dnsString);
} }
public void setDnsString(final String dnsString) { private void setDnsString(final String dnsString) {
this.dnsList.clear(); this.dnsList.clear();
addDnses(Attribute.stringToList(dnsString)); addDnses(Attribute.stringToList(dnsString));
} }
public void setListenPort(int listenPort) { private void setListenPort(int listenPort) {
this.listenPort = listenPort; this.listenPort = listenPort;
notifyPropertyChanged(BR.listenPort);
notifyPropertyChanged(BR.listenPortString);
} }
public void setListenPortString(final String port) { private void setListenPortString(final String port) {
if (port != null && !port.isEmpty()) if (port != null && !port.isEmpty())
setListenPort(Integer.parseInt(port, 10)); setListenPort(Integer.parseInt(port, 10));
else else
setListenPort(0); setListenPort(0);
} }
public void setMtu(int mtu) { private void setMtu(int mtu) {
this.mtu = mtu; this.mtu = mtu;
notifyPropertyChanged(BR.mtu);
notifyPropertyChanged(BR.mtuString);
} }
public void setMtuString(final String mtu) { private void setMtuString(final String mtu) {
if (mtu != null && !mtu.isEmpty()) if (mtu != null && !mtu.isEmpty())
setMtu(Integer.parseInt(mtu, 10)); setMtu(Integer.parseInt(mtu, 10));
else else
setMtu(0); setMtu(0);
} }
public void setPrivateKey(String privateKey) { private void setPrivateKey(String privateKey) {
if (privateKey != null && privateKey.isEmpty()) if (privateKey != null && privateKey.isEmpty())
privateKey = null; privateKey = null;
this.privateKey = privateKey; if (privateKey == null)
if (privateKey != null && privateKey.length() == KeyEncoding.KEY_LENGTH_BASE64) {
try {
keypair = new Keypair(privateKey);
} catch (final IllegalArgumentException e) {
keypair = null;
throw e;
}
} else {
keypair = null; keypair = null;
} else
notifyPropertyChanged(BR.privateKey); keypair = new Keypair(privateKey);
notifyPropertyChanged(BR.publicKey);
} }
@Override @Override
@ -240,8 +309,8 @@ public class Interface extends BaseObservable implements Parcelable {
sb.append(Attribute.LISTEN_PORT.composeWith(listenPort)); sb.append(Attribute.LISTEN_PORT.composeWith(listenPort));
if (mtu != 0) if (mtu != 0)
sb.append(Attribute.MTU.composeWith(mtu)); sb.append(Attribute.MTU.composeWith(mtu));
if (privateKey != null) if (keypair != null)
sb.append(Attribute.PRIVATE_KEY.composeWith(privateKey)); sb.append(Attribute.PRIVATE_KEY.composeWith(keypair.getPrivateKey()));
return sb.toString(); return sb.toString();
} }
@ -253,6 +322,6 @@ public class Interface extends BaseObservable implements Parcelable {
dest.writeByteArray(addr.getAddress()); dest.writeByteArray(addr.getAddress());
dest.writeInt(listenPort); dest.writeInt(listenPort);
dest.writeInt(mtu); dest.writeInt(mtu);
dest.writeString(privateKey); dest.writeString(keypair == null ? "" : keypair.getPrivateKey());
} }
} }

View File

@ -6,6 +6,7 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.android.databinding.library.baseAdapters.BR; import com.android.databinding.library.baseAdapters.BR;
import com.wireguard.crypto.KeyEncoding;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -19,7 +20,7 @@ import java.util.List;
* Represents the configuration for a WireGuard peer (a [Peer] block). * Represents the configuration for a WireGuard peer (a [Peer] block).
*/ */
public class Peer extends BaseObservable implements Parcelable { public class Peer implements Parcelable {
public static final Creator<Peer> CREATOR = new Creator<Peer>() { public static final Creator<Peer> CREATOR = new Creator<Peer>() {
@Override @Override
public Peer createFromParcel(final Parcel in) { public Peer createFromParcel(final Parcel in) {
@ -32,6 +33,92 @@ public class Peer extends BaseObservable implements Parcelable {
} }
}; };
public static class Observable extends BaseObservable {
private String allowedIPs;
private String endpoint;
private String persistentKeepalive;
private String preSharedKey;
private String publicKey;
public Observable(Peer parent) {
loadData(parent);
}
public static Observable newInstance() {
return new Observable(new Peer());
}
public void loadData(Peer parent) {
this.allowedIPs = parent.getAllowedIPsString();
this.endpoint = parent.getEndpointString();
this.persistentKeepalive = parent.getPersistentKeepaliveString();
this.preSharedKey = parent.getPreSharedKey();
this.publicKey = parent.getPublicKey();
}
public void commitData(Peer parent) {
parent.setAllowedIPsString(this.allowedIPs);
parent.setEndpointString(this.endpoint);
parent.setPersistentKeepaliveString(this.persistentKeepalive);
parent.setPreSharedKey(this.preSharedKey);
parent.setPublicKey(this.publicKey);
if (parent.getPublicKey() == null)
throw new IllegalArgumentException("Peer public key may not be empty");
loadData(parent);
notifyChange();
}
@Bindable
public String getAllowedIPs() {
return allowedIPs;
}
public void setAllowedIPs(String allowedIPs) {
this.allowedIPs = allowedIPs;
notifyPropertyChanged(BR.allowedIPs);
}
@Bindable
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
notifyPropertyChanged(BR.endpoint);
}
@Bindable
public String getPersistentKeepalive() {
return persistentKeepalive;
}
public void setPersistentKeepalive(String persistentKeepalive) {
this.persistentKeepalive = persistentKeepalive;
notifyPropertyChanged(BR.persistentKeepalive);
}
@Bindable
public String getPreSharedKey() {
return preSharedKey;
}
public void setPreSharedKey(String preSharedKey) {
this.preSharedKey = preSharedKey;
notifyPropertyChanged(BR.preSharedKey);
}
@Bindable
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
notifyPropertyChanged(BR.publicKey);
}
}
private List<IPCidr> allowedIPsList; private List<IPCidr> allowedIPsList;
private InetSocketAddress endpoint; private InetSocketAddress endpoint;
private int persistentKeepalive; private int persistentKeepalive;
@ -57,35 +144,27 @@ public class Peer extends BaseObservable implements Parcelable {
publicKey = null; publicKey = null;
} }
public static Peer newInstance() {
return new Peer();
}
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;
} }
@Bindable private String getAllowedIPsString() {
public String getAllowedIPsString() {
if (allowedIPsList.isEmpty()) if (allowedIPsList.isEmpty())
return null; return null;
return Attribute.listToString(allowedIPsList); return Attribute.listToString(allowedIPsList);
} }
@Bindable
public IPCidr[] getAllowedIPs() { public IPCidr[] getAllowedIPs() {
return allowedIPsList.toArray(new IPCidr[allowedIPsList.size()]); return allowedIPsList.toArray(new IPCidr[allowedIPsList.size()]);
} }
@Bindable
public InetSocketAddress getEndpoint() { public InetSocketAddress getEndpoint() {
return endpoint; return endpoint;
} }
@Bindable private String getEndpointString() {
public String getEndpointString() {
if (endpoint == null) if (endpoint == null)
return null; return null;
return String.format("%s:%d", endpoint.getHostString(), endpoint.getPort()); return String.format("%s:%d", endpoint.getHostString(), endpoint.getPort());
@ -103,24 +182,20 @@ public class Peer extends BaseObservable implements Parcelable {
return String.format("%s:%d", endpoint.getAddress().getHostAddress(), endpoint.getPort()); return String.format("%s:%d", endpoint.getAddress().getHostAddress(), endpoint.getPort());
} }
@Bindable
public int getPersistentKeepalive() { public int getPersistentKeepalive() {
return persistentKeepalive; return persistentKeepalive;
} }
@Bindable private String getPersistentKeepaliveString() {
public String getPersistentKeepaliveString() {
if (persistentKeepalive == 0) if (persistentKeepalive == 0)
return null; return null;
return new Integer(persistentKeepalive).toString(); return new Integer(persistentKeepalive).toString();
} }
@Bindable
public String getPreSharedKey() { public String getPreSharedKey() {
return preSharedKey; return preSharedKey;
} }
@Bindable
public String getPublicKey() { public String getPublicKey() {
return publicKey; return publicKey;
} }
@ -141,28 +216,24 @@ public class Peer extends BaseObservable implements Parcelable {
throw new IllegalArgumentException(line); throw new IllegalArgumentException(line);
} }
public void addAllowedIPs(String[] allowedIPs) { private void addAllowedIPs(String[] allowedIPs) {
if (allowedIPs != null && allowedIPs.length > 0) { if (allowedIPs != null && allowedIPs.length > 0) {
for (final String allowedIP : allowedIPs) { for (final String allowedIP : allowedIPs) {
this.allowedIPsList.add(new IPCidr(allowedIP)); this.allowedIPsList.add(new IPCidr(allowedIP));
} }
} }
notifyPropertyChanged(BR.allowedIPs);
notifyPropertyChanged(BR.allowedIPsString);
} }
public void setAllowedIPsString(final String allowedIPsString) { private void setAllowedIPsString(final String allowedIPsString) {
this.allowedIPsList.clear(); this.allowedIPsList.clear();
addAllowedIPs(Attribute.stringToList(allowedIPsString)); addAllowedIPs(Attribute.stringToList(allowedIPsString));
} }
public void setEndpoint(InetSocketAddress endpoint) { private void setEndpoint(InetSocketAddress endpoint) {
this.endpoint = endpoint; this.endpoint = endpoint;
notifyPropertyChanged(BR.endpoint);
notifyPropertyChanged(BR.endpointString);
} }
public void setEndpointString(final String endpoint) { private void setEndpointString(final String endpoint) {
if (endpoint != null && !endpoint.isEmpty()) { if (endpoint != null && !endpoint.isEmpty()) {
InetSocketAddress constructedEndpoint; InetSocketAddress constructedEndpoint;
if (endpoint.indexOf('/') != -1 || endpoint.indexOf('?') != -1 || endpoint.indexOf('#') != -1) if (endpoint.indexOf('/') != -1 || endpoint.indexOf('?') != -1 || endpoint.indexOf('#') != -1)
@ -179,31 +250,31 @@ public class Peer extends BaseObservable implements Parcelable {
setEndpoint(null); setEndpoint(null);
} }
public void setPersistentKeepalive(int persistentKeepalive) { private void setPersistentKeepalive(int persistentKeepalive) {
this.persistentKeepalive = persistentKeepalive; this.persistentKeepalive = persistentKeepalive;
notifyPropertyChanged(BR.persistentKeepalive);
notifyPropertyChanged(BR.persistentKeepaliveString);
} }
public void setPersistentKeepaliveString(String persistentKeepalive) { private void setPersistentKeepaliveString(String persistentKeepalive) {
if (persistentKeepalive != null && !persistentKeepalive.isEmpty()) if (persistentKeepalive != null && !persistentKeepalive.isEmpty())
setPersistentKeepalive(Integer.parseInt(persistentKeepalive, 10)); setPersistentKeepalive(Integer.parseInt(persistentKeepalive, 10));
else else
setPersistentKeepalive(0); setPersistentKeepalive(0);
} }
public void setPreSharedKey(String preSharedKey) { private void setPreSharedKey(String preSharedKey) {
if (preSharedKey != null && preSharedKey.isEmpty()) if (preSharedKey != null && preSharedKey.isEmpty())
preSharedKey = null; preSharedKey = null;
if (preSharedKey != null)
KeyEncoding.keyFromBase64(preSharedKey);
this.preSharedKey = preSharedKey; this.preSharedKey = preSharedKey;
notifyPropertyChanged(BR.preSharedKey);
} }
public void setPublicKey(String publicKey) { private void setPublicKey(String publicKey) {
if (publicKey != null && publicKey.isEmpty()) if (publicKey != null && publicKey.isEmpty())
publicKey = null; publicKey = null;
if (publicKey != null)
KeyEncoding.keyFromBase64(publicKey);
this.publicKey = publicKey; this.publicKey = publicKey;
notifyPropertyChanged(BR.publicKey);
} }
@Override @Override

View File

@ -14,6 +14,10 @@
<variable <variable
name="tunnel" name="tunnel"
type="com.wireguard.android.model.Tunnel" /> type="com.wireguard.android.model.Tunnel" />
<variable
name="config"
type="com.wireguard.config.Config.Observable" />
</data> </data>
<ScrollView <ScrollView
@ -92,7 +96,25 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:onClick="@{ClipboardUtils::copyTextView}" android:onClick="@{ClipboardUtils::copyTextView}"
android:text="@{tunnel.config.interface.publicKey}" /> android:text="@{config.interfaceSection.publicKey}" />
<TextView
android:id="@+id/addresses_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/public_key_text"
android:layout_marginTop="8dp"
android:labelFor="@+id/addresses_text"
android:text="@string/addresses" />
<TextView
android:id="@+id/addresses_text"
style="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/addresses_label"
android:contentDescription="@string/addresses"
android:text="@{config.interfaceSection.addresses}" />
</RelativeLayout> </RelativeLayout>
<LinearLayout <LinearLayout
@ -101,7 +123,7 @@
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:divider="@null" android:divider="@null"
android:orientation="vertical" android:orientation="vertical"
app:items="@{tunnel.config.peers}" app:items="@{config.peers}"
app:layout="@{@layout/tunnel_detail_peer}" app:layout="@{@layout/tunnel_detail_peer}"
tools:ignore="UselessLeaf" /> tools:ignore="UselessLeaf" />
</LinearLayout> </LinearLayout>

View File

@ -5,13 +5,9 @@
<import type="com.wireguard.android.util.ClipboardUtils" /> <import type="com.wireguard.android.util.ClipboardUtils" />
<variable
name="collection"
type="android.databinding.ObservableList&lt;com.wireguard.config.Peer&gt;" />
<variable <variable
name="item" name="item"
type="com.wireguard.config.Peer" /> type="com.wireguard.config.Peer.Observable" />
</data> </data>
<RelativeLayout <RelativeLayout
@ -69,7 +65,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/allowed_ips_label" android:layout_below="@+id/allowed_ips_label"
android:text="@{item.allowedIPsString}" /> android:text="@{item.allowedIPs}" />
<TextView <TextView
android:id="@+id/endpoint_label" android:id="@+id/endpoint_label"
@ -86,6 +82,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/endpoint_label" android:layout_below="@+id/endpoint_label"
android:text="@{item.endpointString}" /> android:text="@{item.endpoint}" />
</RelativeLayout> </RelativeLayout>
</layout> </layout>

View File

@ -15,11 +15,7 @@
<variable <variable
name="config" name="config"
type="com.wireguard.config.Config" /> type="com.wireguard.config.Config.Observable" />
<variable
name="name"
type="android.databinding.ObservableField&lt;String&gt;" />
</data> </data>
<com.commonsware.cwac.crossport.design.widget.CoordinatorLayout <com.commonsware.cwac.crossport.design.widget.CoordinatorLayout
@ -71,7 +67,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/interface_name_label" android:layout_below="@+id/interface_name_label"
android:inputType="textNoSuggestions|textVisiblePassword" android:inputType="textNoSuggestions|textVisiblePassword"
android:text="@={name}" android:text="@={config.name}"
app:filter="@{NameInputFilter.newInstance()}" /> app:filter="@{NameInputFilter.newInstance()}" />
<TextView <TextView
@ -91,7 +87,7 @@
android:layout_toStartOf="@+id/generate_private_key_button" android:layout_toStartOf="@+id/generate_private_key_button"
android:contentDescription="@string/public_key_description" android:contentDescription="@string/public_key_description"
android:inputType="textNoSuggestions|textVisiblePassword" android:inputType="textNoSuggestions|textVisiblePassword"
android:text="@={config.interface.privateKey}" android:text="@={config.interfaceSection.privateKey}"
app:filter="@{KeyInputFilter.newInstance()}" /> app:filter="@{KeyInputFilter.newInstance()}" />
<Button <Button
@ -101,7 +97,7 @@
android:layout_alignBottom="@id/private_key_text" android:layout_alignBottom="@id/private_key_text"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_below="@+id/private_key_label" android:layout_below="@+id/private_key_label"
android:onClick="@{() -> config.interface.generateKeypair()}" android:onClick="@{() -> config.interfaceSection.generateKeypair()}"
android:text="@string/generate" /> android:text="@string/generate" />
<TextView <TextView
@ -124,7 +120,7 @@
android:maxLines="1" android:maxLines="1"
android:contentDescription="@string/public_key_description" android:contentDescription="@string/public_key_description"
android:onClick="@{ClipboardUtils::copyTextView}" android:onClick="@{ClipboardUtils::copyTextView}"
android:text="@{config.interface.publicKey}" /> android:text="@{config.interfaceSection.publicKey}" />
<TextView <TextView
android:id="@+id/addresses_label" android:id="@+id/addresses_label"
@ -144,7 +140,7 @@
android:layout_below="@+id/addresses_label" android:layout_below="@+id/addresses_label"
android:layout_toStartOf="@+id/listen_port_text" android:layout_toStartOf="@+id/listen_port_text"
android:inputType="textNoSuggestions|textVisiblePassword" android:inputType="textNoSuggestions|textVisiblePassword"
android:text="@={config.interface.addressString}" /> android:text="@={config.interfaceSection.addresses}" />
<TextView <TextView
android:id="@+id/listen_port_label" android:id="@+id/listen_port_label"
@ -165,7 +161,7 @@
android:layout_alignStart="@+id/generate_private_key_button" android:layout_alignStart="@+id/generate_private_key_button"
android:hint="@string/hint_random" android:hint="@string/hint_random"
android:inputType="number" android:inputType="number"
android:text="@={config.interface.listenPortString}" android:text="@={config.interfaceSection.listenPort}"
android:textAlignment="center" /> android:textAlignment="center" />
<TextView <TextView
@ -186,7 +182,7 @@
android:layout_below="@+id/dns_servers_label" android:layout_below="@+id/dns_servers_label"
android:layout_toStartOf="@+id/mtu_text" android:layout_toStartOf="@+id/mtu_text"
android:inputType="textNoSuggestions|textVisiblePassword" android:inputType="textNoSuggestions|textVisiblePassword"
android:text="@={config.interface.dnsString}" /> android:text="@={config.interfaceSection.dnses}" />
<TextView <TextView
android:id="@+id/mtu_label" android:id="@+id/mtu_label"
@ -207,7 +203,7 @@
android:layout_alignStart="@+id/generate_private_key_button" android:layout_alignStart="@+id/generate_private_key_button"
android:hint="@string/hint_automatic" android:hint="@string/hint_automatic"
android:inputType="number" android:inputType="number"
android:text="@={config.interface.mtuString}" android:text="@={config.interfaceSection.mtu}"
android:textAlignment="center" /> android:textAlignment="center" />
</RelativeLayout> </RelativeLayout>
@ -226,7 +222,7 @@
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:onClick="@{() -> config.peers.add(Peer.newInstance())}" android:onClick="@{() -> config.peers.add(Peer.Observable.newInstance())}"
android:text="@string/add_peer" /> android:text="@string/add_peer" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -8,11 +8,11 @@
<variable <variable
name="collection" name="collection"
type="android.databinding.ObservableList&lt;com.wireguard.config.Peer&gt;" /> type="android.databinding.ObservableList&lt;com.wireguard.config.Peer.Observable&gt;" />
<variable <variable
name="item" name="item"
type="com.wireguard.config.Peer" /> type="com.wireguard.config.Peer.Observable" />
</data> </data>
<RelativeLayout <RelativeLayout
@ -96,7 +96,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/allowed_ips_label" android:layout_below="@+id/allowed_ips_label"
android:inputType="textNoSuggestions|textVisiblePassword" android:inputType="textNoSuggestions|textVisiblePassword"
android:text="@={item.allowedIPsString}" /> android:text="@={item.allowedIPs}" />
<TextView <TextView
android:id="@+id/endpoint_label" android:id="@+id/endpoint_label"
@ -116,7 +116,7 @@
android:layout_below="@+id/endpoint_label" android:layout_below="@+id/endpoint_label"
android:layout_toStartOf="@+id/persistent_keepalive_text" android:layout_toStartOf="@+id/persistent_keepalive_text"
android:inputType="textNoSuggestions|textVisiblePassword" android:inputType="textNoSuggestions|textVisiblePassword"
android:text="@={item.endpointString}" /> android:text="@={item.endpoint}" />
<TextView <TextView
android:id="@+id/persistent_keepalive_label" android:id="@+id/persistent_keepalive_label"
@ -136,7 +136,7 @@
android:layout_alignStart="@+id/persistent_keepalive_label" android:layout_alignStart="@+id/persistent_keepalive_label"
android:hint="@string/hint_optional" android:hint="@string/hint_optional"
android:inputType="number" android:inputType="number"
android:text="@={item.persistentKeepaliveString}" android:text="@={item.persistentKeepalive}"
android:textAlignment="center" /> android:textAlignment="center" />
</RelativeLayout> </RelativeLayout>
</layout> </layout>