diff --git a/ui/build.gradle b/ui/build.gradle index d9f20d3..bc4bd74 100644 --- a/ui/build.gradle +++ b/ui/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' version wireguardVersionName group groupName diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt index 387d096..b6f3043 100644 --- a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt +++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt @@ -83,7 +83,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { override fun onExcludedAppsSelected(excludedApps: List) { requireNotNull(binding) { "Tried to set excluded apps while no view was loaded" } - binding!!.config!!.getInterface().excludedApplications.apply { + binding!!.config!!.`interface`.excludedApplications.apply { clear() addAll(excludedApps) } @@ -125,12 +125,12 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { tunnel == null -> { Log.d(TAG, "Attempting to create new tunnel " + binding!!.name) val manager = Application.getTunnelManager() - manager.create(binding!!.name, newConfig) + manager.create(binding!!.name!!, newConfig) .whenComplete(this::onTunnelCreated) } tunnel!!.name != binding!!.name -> { Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name) - tunnel!!.setName(binding!!.name) + tunnel!!.setName(binding!!.name!!) .whenComplete { _, t -> onTunnelRenamed(tunnel!!, newConfig, t) } } else -> { @@ -147,7 +147,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { @Suppress("UNUSED_PARAMETER") fun onRequestSetExcludedApplications(view: View?) { if (binding != null) { - val excludedApps = ArrayList(binding!!.config!!.getInterface().excludedApplications) + val excludedApps = ArrayList(binding!!.config!!.`interface`.excludedApplications) val fragment = AppListDialogFragment.newInstance(excludedApps, this) fragment.show(parentFragmentManager, null) } diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java b/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java deleted file mode 100644 index d44a597..0000000 --- a/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.viewmodel; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.wireguard.config.BadConfigException; -import com.wireguard.config.Config; -import com.wireguard.config.Peer; -import com.wireguard.util.NonNullForAll; - -import java.util.ArrayList; -import java.util.Collection; - -import androidx.databinding.ObservableArrayList; -import androidx.databinding.ObservableList; - -@NonNullForAll -public class ConfigProxy implements Parcelable { - public static final Parcelable.Creator CREATOR = new ConfigProxyCreator(); - - private final InterfaceProxy interfaze; - private final ObservableList 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 getPeers() { - return peers; - } - - public Config resolve() throws BadConfigException { - final Collection 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 { - @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/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.kt b/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.kt new file mode 100644 index 0000000..36774c3 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.kt @@ -0,0 +1,77 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.wireguard.android.viewmodel + +import android.os.Parcel +import android.os.Parcelable +import androidx.databinding.ObservableArrayList +import androidx.databinding.ObservableList +import com.wireguard.config.BadConfigException +import com.wireguard.config.Config +import com.wireguard.config.Peer +import java.util.ArrayList + +class ConfigProxy : Parcelable { + val `interface`: InterfaceProxy + val peers: ObservableList = ObservableArrayList() + + private constructor(parcel: Parcel) { + `interface` = parcel.readParcelable(InterfaceProxy::class.java.classLoader)!! + parcel.readTypedList(peers, PeerProxy.CREATOR) + peers.forEach { it.bind(this) } + } + + constructor(other: Config) { + `interface` = InterfaceProxy(other.getInterface()) + other.peers.forEach { + val proxy = PeerProxy(it) + peers.add(proxy) + proxy.bind(this) + } + } + + constructor() { + `interface` = InterfaceProxy() + } + + fun addPeer(): PeerProxy { + val proxy = PeerProxy() + peers.add(proxy) + proxy.bind(this) + return proxy + } + + override fun describeContents() = 0 + + @Throws(BadConfigException::class) + fun resolve(): Config { + val resolvedPeers: MutableCollection = ArrayList() + peers.forEach { resolvedPeers.add(it.resolve()) } + return Config.Builder() + .setInterface(`interface`.resolve()) + .addPeers(resolvedPeers) + .build() + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeParcelable(`interface`, flags) + dest.writeTypedList(peers) + } + + private class ConfigProxyCreator : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ConfigProxy { + return ConfigProxy(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + + companion object { + @JvmField + val CREATOR: Parcelable.Creator = ConfigProxyCreator() + } +} diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java b/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java deleted file mode 100644 index 2bf87ea..0000000 --- a/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.viewmodel; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.wireguard.android.BR; -import com.wireguard.config.Attribute; -import com.wireguard.config.BadConfigException; -import com.wireguard.config.Interface; -import com.wireguard.crypto.Key; -import com.wireguard.crypto.KeyFormatException; -import com.wireguard.crypto.KeyPair; -import com.wireguard.util.NonNullForAll; - -import java.net.InetAddress; -import java.util.List; - -import androidx.databinding.BaseObservable; -import androidx.databinding.Bindable; -import androidx.databinding.ObservableArrayList; -import androidx.databinding.ObservableList; -import java9.util.stream.Collectors; -import java9.util.stream.StreamSupport; - -@NonNullForAll -public class InterfaceProxy extends BaseObservable implements Parcelable { - public static final Parcelable.Creator CREATOR = new InterfaceProxyCreator(); - - private final ObservableList 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 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 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 BadConfigException { - 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 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 { - @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/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt b/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt new file mode 100644 index 0000000..53f2fa2 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt @@ -0,0 +1,135 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.wireguard.android.viewmodel + +import android.os.Parcel +import android.os.Parcelable +import androidx.databinding.BaseObservable +import androidx.databinding.Bindable +import androidx.databinding.ObservableArrayList +import androidx.databinding.ObservableList +import com.wireguard.android.BR +import com.wireguard.config.Attribute +import com.wireguard.config.BadConfigException +import com.wireguard.config.Interface +import com.wireguard.crypto.Key +import com.wireguard.crypto.KeyFormatException +import com.wireguard.crypto.KeyPair + +class InterfaceProxy : BaseObservable, Parcelable { + @get:Bindable + val excludedApplications: ObservableList = ObservableArrayList() + + @get:Bindable + var addresses: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.addresses) + } + + @get:Bindable + var dnsServers: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.dnsServers) + } + + @get:Bindable + var listenPort: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.listenPort) + } + + @get:Bindable + var mtu: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.mtu) + } + + @get:Bindable + var privateKey: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.privateKey) + notifyPropertyChanged(BR.publicKey) + } + + @get:Bindable + val publicKey: String + get() = try { + KeyPair(Key.fromBase64(privateKey)).publicKey.toBase64() + } catch (ignored: KeyFormatException) { + "" + } + + private constructor(parcel: Parcel) { + addresses = parcel.readString() ?: "" + dnsServers = parcel.readString() ?: "" + parcel.readStringList(excludedApplications) + listenPort = parcel.readString() ?: "" + mtu = parcel.readString() ?: "" + privateKey = parcel.readString() ?: "" + } + + constructor(other: Interface) { + addresses = Attribute.join(other.addresses) + val dnsServerStrings = other.dnsServers.map { it.hostAddress } + dnsServers = Attribute.join(dnsServerStrings) + excludedApplications.addAll(other.excludedApplications) + listenPort = other.listenPort.map { it.toString() }.orElse("") + mtu = other.mtu.map { it.toString() }.orElse("") + val keyPair = other.keyPair + privateKey = keyPair.privateKey.toBase64() + } + + constructor() + + override fun describeContents() = 0 + + fun generateKeyPair() { + val keyPair = KeyPair() + privateKey = keyPair.privateKey.toBase64() + notifyPropertyChanged(BR.privateKey) + notifyPropertyChanged(BR.publicKey) + } + + @Throws(BadConfigException::class) + fun resolve(): Interface { + val builder = Interface.Builder() + if (addresses.isNotEmpty()) builder.parseAddresses(addresses) + if (dnsServers.isNotEmpty()) builder.parseDnsServers(dnsServers) + if (excludedApplications.isNotEmpty()) builder.excludeApplications(excludedApplications) + if (listenPort.isNotEmpty()) builder.parseListenPort(listenPort) + if (mtu.isNotEmpty()) builder.parseMtu(mtu) + if (privateKey.isNotEmpty()) builder.parsePrivateKey(privateKey) + return builder.build() + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(addresses) + dest.writeString(dnsServers) + dest.writeStringList(excludedApplications) + dest.writeString(listenPort) + dest.writeString(mtu) + dest.writeString(privateKey) + } + + private class InterfaceProxyCreator : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): InterfaceProxy { + return InterfaceProxy(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + + companion object { + @JvmField + val CREATOR: Parcelable.Creator = InterfaceProxyCreator() + } +} diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java b/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java deleted file mode 100644 index 6e01d35..0000000 --- a/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.viewmodel; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.wireguard.android.BR; -import com.wireguard.config.Attribute; -import com.wireguard.config.BadConfigException; -import com.wireguard.config.InetEndpoint; -import com.wireguard.config.Peer; -import com.wireguard.crypto.Key; -import com.wireguard.util.NonNullForAll; - -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 androidx.annotation.Nullable; -import androidx.databinding.BaseObservable; -import androidx.databinding.Bindable; -import androidx.databinding.Observable; -import androidx.databinding.ObservableList; -import java9.util.Lists; -import java9.util.Sets; -import java9.util.stream.Collectors; -import java9.util.stream.Stream; - -@NonNullForAll -public class PeerProxy extends BaseObservable implements Parcelable { - public static final Parcelable.Creator CREATOR = new PeerProxyCreator(); - private static final Set 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 IPV4_WILDCARD = Sets.of("0.0.0.0/0"); - - private final List 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 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 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 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 BadConfigException { - 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 oldNetworks = excludingPrivateIps ? IPV4_WILDCARD : IPV4_PUBLIC_NETWORKS; - final Set newNetworks = excludingPrivateIps ? IPV4_PUBLIC_NETWORKS : IPV4_WILDCARD; - final Collection input = getAllowedIpsSet(); - final int outputSize = input.size() - oldNetworks.size() + newNetworks.size(); - final Collection 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 newDnsRoutes = Stream.of(Attribute.split(dnsServers)) - .filter(server -> !server.contains(":")) - .map(server -> server + "/32") - .collect(Collectors.toUnmodifiableList()); - if (allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS) { - final Collection input = getAllowedIpsSet(); - final Collection 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 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 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> { - private final WeakReference weakPeerProxy; - - private PeerListListener(final PeerProxy peerProxy) { - weakPeerProxy = new WeakReference<>(peerProxy); - } - - @Override - public void onChanged(final ObservableList sender) { - @Nullable final PeerProxy peerProxy = weakPeerProxy.get(); - if (peerProxy == null) { - sender.removeOnListChangedCallback(this); - return; - } - peerProxy.setTotalPeers(sender.size()); - } - - @Override - public void onItemRangeChanged(final ObservableList sender, - final int positionStart, final int itemCount) { - // Do nothing. - } - - @Override - public void onItemRangeInserted(final ObservableList sender, - final int positionStart, final int itemCount) { - onChanged(sender); - } - - @Override - public void onItemRangeMoved(final ObservableList sender, - final int fromPosition, final int toPosition, - final int itemCount) { - // Do nothing. - } - - @Override - public void onItemRangeRemoved(final ObservableList sender, - final int positionStart, final int itemCount) { - onChanged(sender); - } - } - - private static class PeerProxyCreator implements Parcelable.Creator { - @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/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.kt b/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.kt new file mode 100644 index 0000000..6ac04f7 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.kt @@ -0,0 +1,288 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.wireguard.android.viewmodel + +import android.os.Parcel +import android.os.Parcelable +import androidx.databinding.BaseObservable +import androidx.databinding.Bindable +import androidx.databinding.Observable +import androidx.databinding.Observable.OnPropertyChangedCallback +import androidx.databinding.ObservableList +import com.wireguard.android.BR +import com.wireguard.config.Attribute +import com.wireguard.config.BadConfigException +import com.wireguard.config.Peer +import java.lang.ref.WeakReference +import java.util.ArrayList +import java.util.LinkedHashSet + +class PeerProxy : BaseObservable, Parcelable { + private val dnsRoutes: MutableList = ArrayList() + private var allowedIpsState = AllowedIpsState.INVALID + private var interfaceDnsListener: InterfaceDnsListener? = null + private var peerListListener: PeerListListener? = null + private var owner: ConfigProxy? = null + private var totalPeers = 0 + + @get:Bindable + var allowedIps: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.allowedIps) + calculateAllowedIpsState() + } + + @get:Bindable + var endpoint: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.endpoint) + } + + @get:Bindable + var persistentKeepalive: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.persistentKeepalive) + } + + @get:Bindable + var preSharedKey: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.preSharedKey) + } + + @get:Bindable + var publicKey: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.publicKey) + } + + @get:Bindable + val isAbleToExcludePrivateIps: Boolean + get() = allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS || allowedIpsState == AllowedIpsState.CONTAINS_IPV4_WILDCARD + + @get:Bindable + val isExcludingPrivateIps: Boolean + get() = allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS + + private constructor(parcel: Parcel) { + allowedIps = parcel.readString() ?: "" + endpoint = parcel.readString() ?: "" + persistentKeepalive = parcel.readString() ?: "" + preSharedKey = parcel.readString() ?: "" + publicKey = parcel.readString() ?: "" + } + + constructor(other: Peer) { + allowedIps = Attribute.join(other.allowedIps) + endpoint = other.endpoint.map { it.toString() }.orElse("") + persistentKeepalive = other.persistentKeepalive.map { it.toString() }.orElse("") + preSharedKey = other.preSharedKey.map { it.toBase64() }.orElse("") + publicKey = other.publicKey.toBase64() + } + + constructor() + + fun bind(owner: ConfigProxy) { + val interfaze: InterfaceProxy = owner.`interface` + val peers = owner.peers + if (interfaceDnsListener == null) interfaceDnsListener = InterfaceDnsListener(this) + interfaze.addOnPropertyChangedCallback(interfaceDnsListener!!) + setInterfaceDns(interfaze.dnsServers) + if (peerListListener == null) peerListListener = PeerListListener(this) + peers.addOnListChangedCallback(peerListListener) + setTotalPeers(peers.size) + this.owner = owner + } + + private fun calculateAllowedIpsState() { + val newState: 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. + val networkStrings: Collection = getAllowedIpsSet() + // If allowedIps contains both the wildcard and the public networks, then private + // networks aren't excluded! + if (networkStrings.containsAll(IPV4_WILDCARD)) + AllowedIpsState.CONTAINS_IPV4_WILDCARD + else if (networkStrings.containsAll(IPV4_PUBLIC_NETWORKS)) + AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS + else + AllowedIpsState.OTHER + } else { + AllowedIpsState.INVALID + } + if (newState != allowedIpsState) { + allowedIpsState = newState + notifyPropertyChanged(BR.ableToExcludePrivateIps) + notifyPropertyChanged(BR.excludingPrivateIps) + } + } + + override fun describeContents() = 0 + + private fun getAllowedIpsSet() = setOf(*Attribute.split(allowedIps)) + + // Replace the first instance of the wildcard with the public network list, or vice versa. + // DNS servers only need to handled specially when we're excluding private IPs. + fun setExcludingPrivateIps(excludingPrivateIps: Boolean) { + if (!isAbleToExcludePrivateIps || isExcludingPrivateIps == excludingPrivateIps) return + val oldNetworks = if (excludingPrivateIps) IPV4_WILDCARD else IPV4_PUBLIC_NETWORKS + val newNetworks = if (excludingPrivateIps) IPV4_PUBLIC_NETWORKS else IPV4_WILDCARD + val input: Collection = getAllowedIpsSet() + val outputSize = input.size - oldNetworks.size + newNetworks.size + val output: MutableCollection = LinkedHashSet(outputSize) + var replaced = false + // Replace the first instance of the wildcard with the public network list, or vice versa. + for (network in input) { + if (oldNetworks.contains(network)) { + if (!replaced) { + for (replacement in 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 = if (excludingPrivateIps) AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS else AllowedIpsState.CONTAINS_IPV4_WILDCARD + notifyPropertyChanged(BR.allowedIps) + notifyPropertyChanged(BR.excludingPrivateIps) + } + + @Throws(BadConfigException::class) + fun resolve(): Peer { + val builder = Peer.Builder() + if (allowedIps.isNotEmpty()) builder.parseAllowedIPs(allowedIps) + if (endpoint.isNotEmpty()) builder.parseEndpoint(endpoint) + if (persistentKeepalive.isNotEmpty()) builder.parsePersistentKeepalive(persistentKeepalive) + if (preSharedKey.isNotEmpty()) builder.parsePreSharedKey(preSharedKey) + if (publicKey.isNotEmpty()) builder.parsePublicKey(publicKey) + return builder.build() + } + + private fun setInterfaceDns(dnsServers: CharSequence) { + val newDnsRoutes = Attribute.split(dnsServers).filter { !it.contains(":") }.map { "$it/32" } + if (allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS) { + val input = getAllowedIpsSet() + // Yes, this is quadratic in the number of DNS servers, but most users have 1 or 2. + val output = input.filter { !dnsRoutes.contains(it) || newDnsRoutes.contains(it) }.plus(newDnsRoutes).distinct() + // 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) + } + + private fun setTotalPeers(totalPeers: Int) { + if (this.totalPeers == totalPeers) return + this.totalPeers = totalPeers + calculateAllowedIpsState() + } + + fun unbind() { + if (owner == null) return + val interfaze: InterfaceProxy = owner!!.`interface` + val peers = owner!!.peers + if (interfaceDnsListener != null) interfaze.removeOnPropertyChangedCallback(interfaceDnsListener!!) + if (peerListListener != null) peers.removeOnListChangedCallback(peerListListener) + peers.remove(this) + setInterfaceDns("") + setTotalPeers(0) + owner = null + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(allowedIps) + dest.writeString(endpoint) + dest.writeString(persistentKeepalive) + dest.writeString(preSharedKey) + dest.writeString(publicKey) + } + + private enum class AllowedIpsState { + CONTAINS_IPV4_PUBLIC_NETWORKS, CONTAINS_IPV4_WILDCARD, INVALID, OTHER + } + + private class InterfaceDnsListener constructor(peerProxy: PeerProxy) : OnPropertyChangedCallback() { + private val weakPeerProxy: WeakReference = WeakReference(peerProxy) + override fun onPropertyChanged(sender: Observable, propertyId: Int) { + val peerProxy = weakPeerProxy.get() + if (peerProxy == null) { + sender.removeOnPropertyChangedCallback(this) + return + } + // This shouldn't be possible, but try to avoid a ClassCastException anyway. + if (sender !is InterfaceProxy) return + if (!(propertyId == BR._all || propertyId == BR.dnsServers)) return + peerProxy.setInterfaceDns(sender.dnsServers) + } + } + + private class PeerListListener(peerProxy: PeerProxy) : ObservableList.OnListChangedCallback>() { + private val weakPeerProxy: WeakReference = WeakReference(peerProxy) + override fun onChanged(sender: ObservableList) { + val peerProxy = weakPeerProxy.get() + if (peerProxy == null) { + sender.removeOnListChangedCallback(this) + return + } + peerProxy.setTotalPeers(sender.size) + } + + override fun onItemRangeChanged(sender: ObservableList, + positionStart: Int, itemCount: Int) { + // Do nothing. + } + + override fun onItemRangeInserted(sender: ObservableList, + positionStart: Int, itemCount: Int) { + onChanged(sender) + } + + override fun onItemRangeMoved(sender: ObservableList, + fromPosition: Int, toPosition: Int, + itemCount: Int) { + // Do nothing. + } + + override fun onItemRangeRemoved(sender: ObservableList, + positionStart: Int, itemCount: Int) { + onChanged(sender) + } + } + + private class PeerProxyCreator : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): PeerProxy { + return PeerProxy(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + + companion object { + @JvmField + val CREATOR: Parcelable.Creator = PeerProxyCreator() + private val IPV4_PUBLIC_NETWORKS = setOf( + "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 val IPV4_WILDCARD = setOf("0.0.0.0/0") + } +}