global: format code
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
40ebf8006e
commit
2e55e5fd05
@ -9,6 +9,21 @@ import com.wireguard.util.NonNullForAll;
|
|||||||
|
|
||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public final class BackendException extends Exception {
|
public final class BackendException extends Exception {
|
||||||
|
private final Object[] format;
|
||||||
|
private final Reason reason;
|
||||||
|
public BackendException(final Reason reason, final Object... format) {
|
||||||
|
this.reason = reason;
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] getFormat() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reason getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Reason {
|
public enum Reason {
|
||||||
UNKNOWN_KERNEL_MODULE_NAME,
|
UNKNOWN_KERNEL_MODULE_NAME,
|
||||||
WG_QUICK_CONFIG_ERROR_CODE,
|
WG_QUICK_CONFIG_ERROR_CODE,
|
||||||
@ -18,16 +33,4 @@ public final class BackendException extends Exception {
|
|||||||
TUN_CREATION_ERROR,
|
TUN_CREATION_ERROR,
|
||||||
GO_ACTIVATION_ERROR_CODE
|
GO_ACTIVATION_ERROR_CODE
|
||||||
}
|
}
|
||||||
private final Reason reason;
|
|
||||||
private final Object[] format;
|
|
||||||
public BackendException(final Reason reason, final Object ...format) {
|
|
||||||
this.reason = reason;
|
|
||||||
this.format = format;
|
|
||||||
}
|
|
||||||
public Reason getReason() {
|
|
||||||
return reason;
|
|
||||||
}
|
|
||||||
public Object[] getFormat() {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -34,25 +34,21 @@ import java9.util.concurrent.CompletableFuture;
|
|||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public final class GoBackend implements Backend {
|
public final class GoBackend implements Backend {
|
||||||
private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName();
|
private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName();
|
||||||
private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>();
|
|
||||||
public interface AlwaysOnCallback {
|
|
||||||
void alwaysOnTriggered();
|
|
||||||
}
|
|
||||||
@Nullable private static AlwaysOnCallback alwaysOnCallback;
|
@Nullable private static AlwaysOnCallback alwaysOnCallback;
|
||||||
public static void setAlwaysOnCallback(AlwaysOnCallback cb) {
|
private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>();
|
||||||
alwaysOnCallback = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@Nullable private Tunnel currentTunnel;
|
|
||||||
@Nullable private Config currentConfig;
|
@Nullable private Config currentConfig;
|
||||||
|
@Nullable private Tunnel currentTunnel;
|
||||||
private int currentTunnelHandle = -1;
|
private int currentTunnelHandle = -1;
|
||||||
|
|
||||||
public GoBackend(final Context context) {
|
public GoBackend(final Context context) {
|
||||||
SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
|
SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setAlwaysOnCallback(AlwaysOnCallback cb) {
|
||||||
|
alwaysOnCallback = cb;
|
||||||
|
}
|
||||||
|
|
||||||
private static native String wgGetConfig(int handle);
|
private static native String wgGetConfig(int handle);
|
||||||
|
|
||||||
private static native int wgGetSocketV4(int handle);
|
private static native int wgGetSocketV4(int handle);
|
||||||
@ -143,7 +139,7 @@ public final class GoBackend implements Backend {
|
|||||||
setStateInternal(currentTunnel, null, State.DOWN);
|
setStateInternal(currentTunnel, null, State.DOWN);
|
||||||
try {
|
try {
|
||||||
setStateInternal(tunnel, config, state);
|
setStateInternal(tunnel, config, state);
|
||||||
} catch(final Exception e) {
|
} catch (final Exception e) {
|
||||||
if (originalTunnel != null)
|
if (originalTunnel != null)
|
||||||
setStateInternal(originalTunnel, originalConfig, State.UP);
|
setStateInternal(originalTunnel, originalConfig, State.UP);
|
||||||
throw e;
|
throw e;
|
||||||
@ -209,7 +205,7 @@ public final class GoBackend implements Backend {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||||
builder.setMetered(false);
|
builder.setMetered(false);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||||
service.setUnderlyingNetworks(null);
|
service.setUnderlyingNetworks(null);
|
||||||
|
|
||||||
builder.setBlocking(true);
|
builder.setBlocking(true);
|
||||||
try (final ParcelFileDescriptor tun = builder.establish()) {
|
try (final ParcelFileDescriptor tun = builder.establish()) {
|
||||||
@ -246,13 +242,13 @@ public final class GoBackend implements Backend {
|
|||||||
context.startService(new Intent(context, VpnService.class));
|
context.startService(new Intent(context, VpnService.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface AlwaysOnCallback {
|
||||||
|
void alwaysOnTriggered();
|
||||||
|
}
|
||||||
|
|
||||||
public static class VpnService extends android.net.VpnService {
|
public static class VpnService extends android.net.VpnService {
|
||||||
@Nullable private GoBackend owner;
|
@Nullable private GoBackend owner;
|
||||||
|
|
||||||
public void setOwner(final GoBackend owner) {
|
|
||||||
this.owner = owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder getBuilder() {
|
public Builder getBuilder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
@ -290,5 +286,9 @@ public final class GoBackend implements Backend {
|
|||||||
}
|
}
|
||||||
return super.onStartCommand(intent, flags, startId);
|
return super.onStartCommand(intent, flags, startId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOwner(final GoBackend owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,11 @@ import java.util.Map;
|
|||||||
|
|
||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public class Statistics {
|
public class Statistics {
|
||||||
private long lastTouched = SystemClock.elapsedRealtime();
|
|
||||||
private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
|
private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
|
||||||
|
private long lastTouched = SystemClock.elapsedRealtime();
|
||||||
|
|
||||||
Statistics() { }
|
Statistics() {
|
||||||
|
}
|
||||||
|
|
||||||
void add(final Key key, final long rx, final long tx) {
|
void add(final Key key, final long rx, final long tx) {
|
||||||
peerBytes.put(key, Pair.create(rx, tx));
|
peerBytes.put(key, Pair.create(rx, tx));
|
||||||
@ -30,10 +31,6 @@ public class Statistics {
|
|||||||
return SystemClock.elapsedRealtime() - lastTouched > 900;
|
return SystemClock.elapsedRealtime() - lastTouched > 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Key[] peers() {
|
|
||||||
return peerBytes.keySet().toArray(new Key[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long peerRx(final Key peer) {
|
public long peerRx(final Key peer) {
|
||||||
if (!peerBytes.containsKey(peer))
|
if (!peerBytes.containsKey(peer))
|
||||||
return 0;
|
return 0;
|
||||||
@ -46,6 +43,10 @@ public class Statistics {
|
|||||||
return peerBytes.get(peer).second;
|
return peerBytes.get(peer).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Key[] peers() {
|
||||||
|
return peerBytes.keySet().toArray(new Key[0]);
|
||||||
|
}
|
||||||
|
|
||||||
public long totalRx() {
|
public long totalRx() {
|
||||||
long rx = 0;
|
long rx = 0;
|
||||||
for (final Pair<Long, Long> val : peerBytes.values()) {
|
for (final Pair<Long, Long> val : peerBytes.values()) {
|
||||||
|
@ -15,16 +15,6 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public interface Tunnel {
|
public interface Tunnel {
|
||||||
enum State {
|
|
||||||
DOWN,
|
|
||||||
TOGGLE,
|
|
||||||
UP;
|
|
||||||
|
|
||||||
public static State of(final boolean running) {
|
|
||||||
return running ? UP : DOWN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int NAME_MAX_LENGTH = 15;
|
int NAME_MAX_LENGTH = 15;
|
||||||
Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}");
|
Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}");
|
||||||
|
|
||||||
@ -46,4 +36,14 @@ public interface Tunnel {
|
|||||||
* @return The new state of the tunnel.
|
* @return The new state of the tunnel.
|
||||||
*/
|
*/
|
||||||
void onStateChange(State newState);
|
void onStateChange(State newState);
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
DOWN,
|
||||||
|
TOGGLE,
|
||||||
|
UP;
|
||||||
|
|
||||||
|
public static State of(final boolean running) {
|
||||||
|
return running ? UP : DOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,10 @@ import java9.util.stream.Stream;
|
|||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public final class WgQuickBackend implements Backend {
|
public final class WgQuickBackend implements Backend {
|
||||||
private static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName();
|
private static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName();
|
||||||
|
|
||||||
private final RootShell rootShell;
|
|
||||||
private final ToolsInstaller toolsInstaller;
|
|
||||||
private final File localTemporaryDir;
|
private final File localTemporaryDir;
|
||||||
|
private final RootShell rootShell;
|
||||||
private final Map<Tunnel, Config> runningConfigs = new HashMap<>();
|
private final Map<Tunnel, Config> runningConfigs = new HashMap<>();
|
||||||
|
private final ToolsInstaller toolsInstaller;
|
||||||
private boolean multipleTunnels;
|
private boolean multipleTunnels;
|
||||||
|
|
||||||
public WgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) {
|
public WgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) {
|
||||||
@ -55,10 +54,6 @@ public final class WgQuickBackend implements Backend {
|
|||||||
this.toolsInstaller = toolsInstaller;
|
this.toolsInstaller = toolsInstaller;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMultipleTunnels(boolean on) {
|
|
||||||
multipleTunnels = on;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getRunningTunnelNames() {
|
public Set<String> getRunningTunnelNames() {
|
||||||
final List<String> output = new ArrayList<>();
|
final List<String> output = new ArrayList<>();
|
||||||
@ -110,6 +105,10 @@ public final class WgQuickBackend implements Backend {
|
|||||||
return output.get(0);
|
return output.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMultipleTunnels(boolean on) {
|
||||||
|
multipleTunnels = on;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception {
|
public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception {
|
||||||
final State originalState = getState(tunnel);
|
final State originalState = getState(tunnel);
|
||||||
@ -135,7 +134,8 @@ public final class WgQuickBackend implements Backend {
|
|||||||
for (final Pair<Tunnel, Config> entry : rewind) {
|
for (final Pair<Tunnel, Config> entry : rewind) {
|
||||||
setStateInternal(entry.first, entry.second, State.UP);
|
setStateInternal(entry.first, entry.second, State.UP);
|
||||||
}
|
}
|
||||||
} catch (final Exception ignored) { }
|
} catch (final Exception ignored) {
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,7 +153,8 @@ public final class WgQuickBackend implements Backend {
|
|||||||
setStateInternal(entry.getKey(), entry.getValue(), State.UP);
|
setStateInternal(entry.getKey(), entry.getValue(), State.UP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final Exception ignored) { }
|
} catch (final Exception ignored) {
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} else if (state == State.DOWN) {
|
} else if (state == State.DOWN) {
|
||||||
|
@ -39,15 +39,14 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public class ModuleLoader {
|
public class ModuleLoader {
|
||||||
private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If";
|
|
||||||
private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig";
|
private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig";
|
||||||
private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s";
|
|
||||||
private static final String MODULE_NAME = "wireguard-%s.ko";
|
private static final String MODULE_NAME = "wireguard-%s.ko";
|
||||||
|
private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If";
|
||||||
private final RootShell rootShell;
|
private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s";
|
||||||
private final String userAgent;
|
|
||||||
private final File moduleDir;
|
private final File moduleDir;
|
||||||
|
private final RootShell rootShell;
|
||||||
private final File tmpDir;
|
private final File tmpDir;
|
||||||
|
private final String userAgent;
|
||||||
|
|
||||||
public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) {
|
public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) {
|
||||||
moduleDir = new File(context.getCacheDir(), "kmod");
|
moduleDir = new File(context.getCacheDir(), "kmod");
|
||||||
@ -56,27 +55,75 @@ public class ModuleLoader {
|
|||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean moduleMightExist() {
|
public static boolean isModuleLoaded() {
|
||||||
return moduleDir.exists() && moduleDir.isDirectory();
|
return new File("/sys/module/wireguard").exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer download() throws IOException, RootShellException, NoSuchAlgorithmException {
|
||||||
|
final List<String> output = new ArrayList<>();
|
||||||
|
rootShell.run(output, "sha256sum /proc/version|cut -d ' ' -f 1");
|
||||||
|
if (output.size() != 1 || output.get(0).length() != 64)
|
||||||
|
throw new InvalidParameterException("Invalid sha256 of /proc/version");
|
||||||
|
final String moduleName = String.format(MODULE_NAME, output.get(0));
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(MODULE_LIST_URL).openConnection();
|
||||||
|
connection.setRequestProperty("User-Agent", userAgent);
|
||||||
|
connection.connect();
|
||||||
|
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||||
|
throw new IOException("Hash list could not be found");
|
||||||
|
byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */];
|
||||||
|
int len;
|
||||||
|
try (final InputStream inputStream = connection.getInputStream()) {
|
||||||
|
len = inputStream.read(input);
|
||||||
|
}
|
||||||
|
if (len <= 0)
|
||||||
|
throw new IOException("Hash list was empty");
|
||||||
|
final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8));
|
||||||
|
if (modules == null)
|
||||||
|
throw new InvalidParameterException("The signature did not verify or invalid hash list format");
|
||||||
|
if (!modules.containsKey(moduleName))
|
||||||
|
return OsConstants.ENOENT;
|
||||||
|
connection = (HttpURLConnection) new URL(String.format(MODULE_URL, moduleName)).openConnection();
|
||||||
|
connection.setRequestProperty("User-Agent", userAgent);
|
||||||
|
connection.connect();
|
||||||
|
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||||
|
throw new IOException("Module file could not be found, despite being on hash list");
|
||||||
|
|
||||||
|
tmpDir.mkdirs();
|
||||||
|
moduleDir.mkdir();
|
||||||
|
File tempFile = null;
|
||||||
|
try {
|
||||||
|
tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir);
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
try (final InputStream inputStream = connection.getInputStream();
|
||||||
|
final FileOutputStream outputStream = new FileOutputStream(tempFile)) {
|
||||||
|
int total = 0;
|
||||||
|
while ((len = inputStream.read(input)) > 0) {
|
||||||
|
total += len;
|
||||||
|
if (total > 1024 * 1024 * 15 /* 15 MiB */)
|
||||||
|
throw new IOException("File too big");
|
||||||
|
outputStream.write(input, 0, len);
|
||||||
|
digest.update(input, 0, len);
|
||||||
|
}
|
||||||
|
outputStream.getFD().sync();
|
||||||
|
}
|
||||||
|
if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes))
|
||||||
|
throw new IOException("Incorrect file hash");
|
||||||
|
|
||||||
|
if (!tempFile.renameTo(new File(moduleDir, moduleName)))
|
||||||
|
throw new IOException("Unable to rename to final destination");
|
||||||
|
} finally {
|
||||||
|
if (tempFile != null)
|
||||||
|
tempFile.delete();
|
||||||
|
}
|
||||||
|
return OsConstants.EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadModule() throws IOException, RootShellException {
|
public void loadModule() throws IOException, RootShellException {
|
||||||
rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath()));
|
rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isModuleLoaded() {
|
public boolean moduleMightExist() {
|
||||||
return new File("/sys/module/wireguard").exists();
|
return moduleDir.exists() && moduleDir.isDirectory();
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Sha256Digest {
|
|
||||||
private byte[] bytes;
|
|
||||||
private Sha256Digest(final String hex) {
|
|
||||||
if (hex.length() != 64)
|
|
||||||
throw new InvalidParameterException("SHA256 hashes must be 32 bytes long");
|
|
||||||
bytes = new byte[32];
|
|
||||||
for (int i = 0; i < 32; ++i)
|
|
||||||
bytes[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -127,62 +174,15 @@ public class ModuleLoader {
|
|||||||
return hashes;
|
return hashes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer download() throws IOException, RootShellException, NoSuchAlgorithmException {
|
private static final class Sha256Digest {
|
||||||
final List<String> output = new ArrayList<>();
|
private byte[] bytes;
|
||||||
rootShell.run(output, "sha256sum /proc/version|cut -d ' ' -f 1");
|
|
||||||
if (output.size() != 1 || output.get(0).length() != 64)
|
|
||||||
throw new InvalidParameterException("Invalid sha256 of /proc/version");
|
|
||||||
final String moduleName = String.format(MODULE_NAME, output.get(0));
|
|
||||||
HttpURLConnection connection = (HttpURLConnection)new URL(MODULE_LIST_URL).openConnection();
|
|
||||||
connection.setRequestProperty("User-Agent", userAgent);
|
|
||||||
connection.connect();
|
|
||||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
|
||||||
throw new IOException("Hash list could not be found");
|
|
||||||
byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */];
|
|
||||||
int len;
|
|
||||||
try (final InputStream inputStream = connection.getInputStream()) {
|
|
||||||
len = inputStream.read(input);
|
|
||||||
}
|
|
||||||
if (len <= 0)
|
|
||||||
throw new IOException("Hash list was empty");
|
|
||||||
final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8));
|
|
||||||
if (modules == null)
|
|
||||||
throw new InvalidParameterException("The signature did not verify or invalid hash list format");
|
|
||||||
if (!modules.containsKey(moduleName))
|
|
||||||
return OsConstants.ENOENT;
|
|
||||||
connection = (HttpURLConnection)new URL(String.format(MODULE_URL, moduleName)).openConnection();
|
|
||||||
connection.setRequestProperty("User-Agent", userAgent);
|
|
||||||
connection.connect();
|
|
||||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
|
||||||
throw new IOException("Module file could not be found, despite being on hash list");
|
|
||||||
|
|
||||||
tmpDir.mkdirs();
|
private Sha256Digest(final String hex) {
|
||||||
moduleDir.mkdir();
|
if (hex.length() != 64)
|
||||||
File tempFile = null;
|
throw new InvalidParameterException("SHA256 hashes must be 32 bytes long");
|
||||||
try {
|
bytes = new byte[32];
|
||||||
tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir);
|
for (int i = 0; i < 32; ++i)
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
||||||
try (final InputStream inputStream = connection.getInputStream();
|
|
||||||
final FileOutputStream outputStream = new FileOutputStream(tempFile)) {
|
|
||||||
int total = 0;
|
|
||||||
while ((len = inputStream.read(input)) > 0) {
|
|
||||||
total += len;
|
|
||||||
if (total > 1024 * 1024 * 15 /* 15 MiB */)
|
|
||||||
throw new IOException("File too big");
|
|
||||||
outputStream.write(input, 0, len);
|
|
||||||
digest.update(input, 0, len);
|
|
||||||
}
|
|
||||||
outputStream.getFD().sync();
|
|
||||||
}
|
|
||||||
if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes))
|
|
||||||
throw new IOException("Incorrect file hash");
|
|
||||||
|
|
||||||
if (!tempFile.renameTo(new File(moduleDir, moduleName)))
|
|
||||||
throw new IOException("Unable to rename to final destination");
|
|
||||||
} finally {
|
|
||||||
if (tempFile != null)
|
|
||||||
tempFile.delete();
|
|
||||||
}
|
}
|
||||||
return OsConstants.EXIT_SUCCESS;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,6 +187,25 @@ public class RootShell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class RootShellException extends Exception {
|
public static class RootShellException extends Exception {
|
||||||
|
private final Object[] format;
|
||||||
|
private final Reason reason;
|
||||||
|
public RootShellException(final Reason reason, final Object... format) {
|
||||||
|
this.reason = reason;
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] getFormat() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reason getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIORelated() {
|
||||||
|
return reason != Reason.NO_ROOT_ACCESS;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Reason {
|
public enum Reason {
|
||||||
NO_ROOT_ACCESS,
|
NO_ROOT_ACCESS,
|
||||||
SHELL_MARKER_COUNT_ERROR,
|
SHELL_MARKER_COUNT_ERROR,
|
||||||
@ -195,20 +214,5 @@ public class RootShell {
|
|||||||
CREATE_BIN_DIR_ERROR,
|
CREATE_BIN_DIR_ERROR,
|
||||||
CREATE_TEMP_DIR_ERROR
|
CREATE_TEMP_DIR_ERROR
|
||||||
}
|
}
|
||||||
private final Reason reason;
|
|
||||||
private final Object[] format;
|
|
||||||
public RootShellException(final Reason reason, final Object ...format) {
|
|
||||||
this.reason = reason;
|
|
||||||
this.format = format;
|
|
||||||
}
|
|
||||||
public boolean isIORelated() {
|
|
||||||
return reason != Reason.NO_ROOT_ACCESS;
|
|
||||||
}
|
|
||||||
public Reason getReason() {
|
|
||||||
return reason;
|
|
||||||
}
|
|
||||||
public Object[] getFormat() {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,9 @@ public final class ToolsInstaller {
|
|||||||
private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName();
|
private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName();
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final RootShell rootShell;
|
|
||||||
private final File localBinaryDir;
|
private final File localBinaryDir;
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
|
private final RootShell rootShell;
|
||||||
@Nullable private Boolean areToolsAvailable;
|
@Nullable private Boolean areToolsAvailable;
|
||||||
@Nullable private Boolean installAsMagiskModule;
|
@Nullable private Boolean installAsMagiskModule;
|
||||||
|
|
||||||
@ -107,6 +107,29 @@ public final class ToolsInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean extract() throws IOException {
|
||||||
|
localBinaryDir.mkdirs();
|
||||||
|
final File files[] = new File[EXECUTABLES.length];
|
||||||
|
final File tempFiles[] = new File[EXECUTABLES.length];
|
||||||
|
boolean allExist = true;
|
||||||
|
for (int i = 0; i < files.length; ++i) {
|
||||||
|
files[i] = new File(localBinaryDir, EXECUTABLES[i]);
|
||||||
|
tempFiles[i] = new File(localBinaryDir, EXECUTABLES[i] + ".tmp");
|
||||||
|
allExist &= files[i].exists();
|
||||||
|
}
|
||||||
|
if (allExist)
|
||||||
|
return false;
|
||||||
|
for (int i = 0; i < files.length; ++i) {
|
||||||
|
if (!SharedLibraryLoader.extractLibrary(context, EXECUTABLES[i], tempFiles[i]))
|
||||||
|
throw new FileNotFoundException("Unable to find " + EXECUTABLES[i]);
|
||||||
|
if (!tempFiles[i].setExecutable(true, false))
|
||||||
|
throw new IOException("Unable to mark " + tempFiles[i].getAbsolutePath() + " as executable");
|
||||||
|
if (!tempFiles[i].renameTo(files[i]))
|
||||||
|
throw new IOException("Unable to rename " + tempFiles[i].getAbsolutePath() + " to " + files[i].getAbsolutePath());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public int install() throws RootShellException, IOException {
|
public int install() throws RootShellException, IOException {
|
||||||
if (!context.getPackageName().startsWith("com.wireguard."))
|
if (!context.getPackageName().startsWith("com.wireguard."))
|
||||||
throw new SecurityException("The tools may only be installed system-wide from the main WireGuard app.");
|
throw new SecurityException("The tools may only be installed system-wide from the main WireGuard app.");
|
||||||
@ -161,29 +184,6 @@ public final class ToolsInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean extract() throws IOException {
|
|
||||||
localBinaryDir.mkdirs();
|
|
||||||
final File files[] = new File[EXECUTABLES.length];
|
|
||||||
final File tempFiles[] = new File[EXECUTABLES.length];
|
|
||||||
boolean allExist = true;
|
|
||||||
for (int i = 0; i < files.length; ++i) {
|
|
||||||
files[i] = new File(localBinaryDir, EXECUTABLES[i]);
|
|
||||||
tempFiles[i] = new File(localBinaryDir, EXECUTABLES[i] + ".tmp");
|
|
||||||
allExist &= files[i].exists();
|
|
||||||
}
|
|
||||||
if (allExist)
|
|
||||||
return false;
|
|
||||||
for (int i = 0; i < files.length; ++i) {
|
|
||||||
if (!SharedLibraryLoader.extractLibrary(context, EXECUTABLES[i], tempFiles[i]))
|
|
||||||
throw new FileNotFoundException("Unable to find " + EXECUTABLES[i]);
|
|
||||||
if (!tempFiles[i].setExecutable(true, false))
|
|
||||||
throw new IOException("Unable to mark " + tempFiles[i].getAbsolutePath() + " as executable");
|
|
||||||
if (!tempFiles[i].renameTo(files[i]))
|
|
||||||
throw new IOException("Unable to rename " + tempFiles[i].getAbsolutePath() + " to " + files[i].getAbsolutePath());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean willInstallAsMagiskModule() {
|
private boolean willInstallAsMagiskModule() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (installAsMagiskModule == null) {
|
if (installAsMagiskModule == null) {
|
||||||
|
@ -35,7 +35,8 @@ public final class InetAddresses {
|
|||||||
PARSER_METHOD = m;
|
PARSER_METHOD = m;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InetAddresses() { }
|
private InetAddresses() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a numeric IPv4 or IPv6 address without performing any DNS lookups.
|
* Parses a numeric IPv4 or IPv6 address without performing any DNS lookups.
|
||||||
|
@ -10,6 +10,7 @@ import com.wireguard.util.NonNullForAll;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public class ParseException extends Exception {
|
public class ParseException extends Exception {
|
||||||
|
@ -204,6 +204,16 @@ public final class Key {
|
|||||||
return new Key(publicKey);
|
return new Key(publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (obj == this)
|
||||||
|
return true;
|
||||||
|
if (obj == null || obj.getClass() != getClass())
|
||||||
|
return false;
|
||||||
|
final Key other = (Key) obj;
|
||||||
|
return MessageDigest.isEqual(key, other.key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the key as an array of bytes.
|
* Returns the key as an array of bytes.
|
||||||
*
|
*
|
||||||
@ -214,6 +224,14 @@ public final class Key {
|
|||||||
return Arrays.copyOf(key, key.length);
|
return Arrays.copyOf(key, key.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int ret = 0;
|
||||||
|
for (int i = 0; i < key.length / 4; ++i)
|
||||||
|
ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16) + (key[i * 4 + 3] >> 24);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes the key to base64.
|
* Encodes the key to base64.
|
||||||
*
|
*
|
||||||
@ -250,24 +268,6 @@ public final class Key {
|
|||||||
return new String(output);
|
return new String(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int ret = 0;
|
|
||||||
for (int i = 0; i < key.length / 4; ++i)
|
|
||||||
ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16) + (key[i * 4 + 3] >> 24);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(final Object obj) {
|
|
||||||
if (obj == this)
|
|
||||||
return true;
|
|
||||||
if (obj == null || obj.getClass() != getClass())
|
|
||||||
return false;
|
|
||||||
final Key other = (Key) obj;
|
|
||||||
return MessageDigest.isEqual(key, other.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The supported formats for encoding a WireGuard key.
|
* The supported formats for encoding a WireGuard key.
|
||||||
*/
|
*/
|
||||||
|
@ -37,17 +37,16 @@ import java9.util.concurrent.CompletableFuture;
|
|||||||
|
|
||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public class Application extends android.app.Application implements SharedPreferences.OnSharedPreferenceChangeListener {
|
public class Application extends android.app.Application implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private static final String TAG = "WireGuard/" + Application.class.getSimpleName();
|
|
||||||
public static final String USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, Build.SUPPORTED_ABIS.length > 0 ? Build.SUPPORTED_ABIS[0] : "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT);
|
public static final String USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, Build.SUPPORTED_ABIS.length > 0 ? Build.SUPPORTED_ABIS[0] : "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT);
|
||||||
|
private static final String TAG = "WireGuard/" + Application.class.getSimpleName();
|
||||||
@SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
|
@SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
|
||||||
private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
|
private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
|
||||||
@SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
|
@SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
|
||||||
@Nullable private Backend backend;
|
@Nullable private Backend backend;
|
||||||
|
@SuppressWarnings("NullableProblems") private ModuleLoader moduleLoader;
|
||||||
@SuppressWarnings("NullableProblems") private RootShell rootShell;
|
@SuppressWarnings("NullableProblems") private RootShell rootShell;
|
||||||
@SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences;
|
@SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences;
|
||||||
@SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller;
|
@SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller;
|
||||||
@SuppressWarnings("NullableProblems") private ModuleLoader moduleLoader;
|
|
||||||
@SuppressWarnings("NullableProblems") private TunnelManager tunnelManager;
|
@SuppressWarnings("NullableProblems") private TunnelManager tunnelManager;
|
||||||
|
|
||||||
public Application() {
|
public Application() {
|
||||||
@ -102,6 +101,10 @@ public class Application extends android.app.Application implements SharedPrefer
|
|||||||
return get().futureBackend;
|
return get().futureBackend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ModuleLoader getModuleLoader() {
|
||||||
|
return get().moduleLoader;
|
||||||
|
}
|
||||||
|
|
||||||
public static RootShell getRootShell() {
|
public static RootShell getRootShell() {
|
||||||
return get().rootShell;
|
return get().rootShell;
|
||||||
}
|
}
|
||||||
@ -114,10 +117,6 @@ public class Application extends android.app.Application implements SharedPrefer
|
|||||||
return get().toolsInstaller;
|
return get().toolsInstaller;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ModuleLoader getModuleLoader() {
|
|
||||||
return get().moduleLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TunnelManager getTunnelManager() {
|
public static TunnelManager getTunnelManager() {
|
||||||
return get().tunnelManager;
|
return get().tunnelManager;
|
||||||
}
|
}
|
||||||
@ -167,15 +166,15 @@ public class Application extends android.app.Application implements SharedPrefer
|
|||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
|
||||||
|
if ("multiple_tunnels".equals(key) && backend != null && backend instanceof WgQuickBackend)
|
||||||
|
((WgQuickBackend) backend).setMultipleTunnels(sharedPreferences.getBoolean(key, false));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTerminate() {
|
public void onTerminate() {
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
||||||
super.onTerminate();
|
super.onTerminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
|
|
||||||
if ("multiple_tunnels".equals(key) && backend != null && backend instanceof WgQuickBackend)
|
|
||||||
((WgQuickBackend)backend).setMultipleTunnels(sharedPreferences.getBoolean(key, false));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,20 @@ import androidx.databinding.DataBindingUtil;
|
|||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public class TunnelDetailFragment extends BaseFragment {
|
public class TunnelDetailFragment extends BaseFragment {
|
||||||
@Nullable private TunnelDetailFragmentBinding binding;
|
@Nullable private TunnelDetailFragmentBinding binding;
|
||||||
@Nullable private Timer timer;
|
|
||||||
@Nullable private State lastState = State.TOGGLE;
|
@Nullable private State lastState = State.TOGGLE;
|
||||||
|
@Nullable private Timer timer;
|
||||||
|
|
||||||
|
private String formatBytes(final long bytes) {
|
||||||
|
if (bytes < 1024)
|
||||||
|
return requireContext().getString(R.string.transfer_bytes, bytes);
|
||||||
|
else if (bytes < 1024 * 1024)
|
||||||
|
return requireContext().getString(R.string.transfer_kibibytes, bytes / 1024.0);
|
||||||
|
else if (bytes < 1024 * 1024 * 1024)
|
||||||
|
return requireContext().getString(R.string.transfer_mibibytes, bytes / (1024.0 * 1024.0));
|
||||||
|
else if (bytes < 1024 * 1024 * 1024 * 1024)
|
||||||
|
return requireContext().getString(R.string.transfer_gibibytes, bytes / (1024.0 * 1024.0 * 1024.0));
|
||||||
|
return requireContext().getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
@ -48,27 +60,6 @@ public class TunnelDetailFragment extends BaseFragment {
|
|||||||
inflater.inflate(R.menu.tunnel_detail, menu);
|
inflater.inflate(R.menu.tunnel_detail, menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
if (timer != null) {
|
|
||||||
timer.cancel();
|
|
||||||
timer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
timer = new Timer();
|
|
||||||
timer.scheduleAtFixedRate(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
updateStats();
|
|
||||||
}
|
|
||||||
}, 0, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
@Nullable final Bundle savedInstanceState) {
|
@Nullable final Bundle savedInstanceState) {
|
||||||
@ -86,6 +77,18 @@ public class TunnelDetailFragment extends BaseFragment {
|
|||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
timer = new Timer();
|
||||||
|
timer.scheduleAtFixedRate(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateStats();
|
||||||
|
}
|
||||||
|
}, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) {
|
public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) {
|
||||||
if (binding == null)
|
if (binding == null)
|
||||||
@ -99,6 +102,15 @@ public class TunnelDetailFragment extends BaseFragment {
|
|||||||
updateStats();
|
updateStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if (timer != null) {
|
||||||
|
timer.cancel();
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
|
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
|
||||||
if (binding == null) {
|
if (binding == null) {
|
||||||
@ -110,18 +122,6 @@ public class TunnelDetailFragment extends BaseFragment {
|
|||||||
super.onViewStateRestored(savedInstanceState);
|
super.onViewStateRestored(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatBytes(final long bytes) {
|
|
||||||
if (bytes < 1024)
|
|
||||||
return requireContext().getString(R.string.transfer_bytes, bytes);
|
|
||||||
else if (bytes < 1024*1024)
|
|
||||||
return requireContext().getString(R.string.transfer_kibibytes, bytes/1024.0);
|
|
||||||
else if (bytes < 1024*1024*1024)
|
|
||||||
return requireContext().getString(R.string.transfer_mibibytes, bytes/(1024.0*1024.0));
|
|
||||||
else if (bytes < 1024*1024*1024*1024)
|
|
||||||
return requireContext().getString(R.string.transfer_gibibytes, bytes/(1024.0*1024.0*1024.0));
|
|
||||||
return requireContext().getString(R.string.transfer_tibibytes, bytes/(1024.0*1024.0*1024.0)/1024.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateStats() {
|
private void updateStats() {
|
||||||
if (binding == null || !isResumed())
|
if (binding == null || !isResumed())
|
||||||
return;
|
return;
|
||||||
|
@ -262,14 +262,6 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSnackbar(final CharSequence message) {
|
|
||||||
if (binding != null) {
|
|
||||||
final Snackbar snackbar = Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG);
|
|
||||||
snackbar.setAnchorView(binding.createFab);
|
|
||||||
snackbar.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) {
|
private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) {
|
||||||
final String message;
|
final String message;
|
||||||
if (throwable == null) {
|
if (throwable == null) {
|
||||||
@ -337,6 +329,14 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showSnackbar(final CharSequence message) {
|
||||||
|
if (binding != null) {
|
||||||
|
final Snackbar snackbar = Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG);
|
||||||
|
snackbar.setAnchorView(binding.createFab);
|
||||||
|
snackbar.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MultiselectableRelativeLayout viewForTunnel(final ObservableTunnel tunnel, final List tunnels) {
|
private MultiselectableRelativeLayout viewForTunnel(final ObservableTunnel tunnel, final List tunnels) {
|
||||||
return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView;
|
return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView;
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,12 @@ import java9.util.concurrent.CompletionStage;
|
|||||||
public class ObservableTunnel extends BaseObservable implements Keyed<String>, Tunnel {
|
public class ObservableTunnel extends BaseObservable implements Keyed<String>, Tunnel {
|
||||||
private final TunnelManager manager;
|
private final TunnelManager manager;
|
||||||
@Nullable private Config config;
|
@Nullable private Config config;
|
||||||
private State state;
|
|
||||||
private String name;
|
private String name;
|
||||||
|
private State state;
|
||||||
@Nullable private Statistics statistics;
|
@Nullable private Statistics statistics;
|
||||||
|
|
||||||
ObservableTunnel(final TunnelManager manager, final String name,
|
ObservableTunnel(final TunnelManager manager, final String name,
|
||||||
@Nullable final Config config, final State state) {
|
@Nullable final Config config, final State state) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
@ -103,6 +103,11 @@ public class ObservableTunnel extends BaseObservable implements Keyed<String>, T
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStateChange(final State newState) {
|
||||||
|
onStateChanged(newState);
|
||||||
|
}
|
||||||
|
|
||||||
State onStateChanged(final State state) {
|
State onStateChanged(final State state) {
|
||||||
if (state != State.UP)
|
if (state != State.UP)
|
||||||
onStatisticsChanged(null);
|
onStatisticsChanged(null);
|
||||||
@ -111,11 +116,6 @@ public class ObservableTunnel extends BaseObservable implements Keyed<String>, T
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStateChange(final State newState) {
|
|
||||||
onStateChanged(newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Statistics onStatisticsChanged(@Nullable final Statistics statistics) {
|
Statistics onStatisticsChanged(@Nullable final Statistics statistics) {
|
||||||
this.statistics = statistics;
|
this.statistics = statistics;
|
||||||
|
@ -47,8 +47,7 @@ public class LogExporterPreference extends Preference {
|
|||||||
final Process process = Runtime.getRuntime().exec(new String[]{
|
final Process process = Runtime.getRuntime().exec(new String[]{
|
||||||
"logcat", "-b", "all", "-d", "-v", "threadtime", "*:V"});
|
"logcat", "-b", "all", "-d", "-v", "threadtime", "*:V"});
|
||||||
try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||||
final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream())))
|
final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
||||||
{
|
|
||||||
String line;
|
String line;
|
||||||
while ((line = stdout.readLine()) != null) {
|
while ((line = stdout.readLine()) != null) {
|
||||||
outputFile.getOutputStream().write(line.getBytes());
|
outputFile.getOutputStream().write(line.getBytes());
|
||||||
|
@ -28,14 +28,6 @@ import androidx.preference.Preference;
|
|||||||
public class VersionPreference extends Preference {
|
public class VersionPreference extends Preference {
|
||||||
@Nullable private String versionSummary;
|
@Nullable private String versionSummary;
|
||||||
|
|
||||||
private String getBackendPrettyName(final Context context, final Backend backend) {
|
|
||||||
if (backend instanceof GoBackend)
|
|
||||||
return context.getString(R.string.type_name_kernel_module);
|
|
||||||
if (backend instanceof WgQuickBackend)
|
|
||||||
return context.getString(R.string.type_name_go_userspace);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public VersionPreference(final Context context, final AttributeSet attrs) {
|
public VersionPreference(final Context context, final AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
|
|
||||||
@ -50,6 +42,14 @@ public class VersionPreference extends Preference {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getBackendPrettyName(final Context context, final Backend backend) {
|
||||||
|
if (backend instanceof GoBackend)
|
||||||
|
return context.getString(R.string.type_name_kernel_module);
|
||||||
|
if (backend instanceof WgQuickBackend)
|
||||||
|
return context.getString(R.string.type_name_go_userspace);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public CharSequence getSummary() {
|
public CharSequence getSummary() {
|
||||||
|
@ -26,30 +26,6 @@ import java.io.OutputStream;
|
|||||||
@NonNullForAll
|
@NonNullForAll
|
||||||
public class DownloadsFileSaver {
|
public class DownloadsFileSaver {
|
||||||
|
|
||||||
public static class DownloadsFile {
|
|
||||||
private Context context;
|
|
||||||
private OutputStream outputStream;
|
|
||||||
private String fileName;
|
|
||||||
private Uri uri;
|
|
||||||
|
|
||||||
private DownloadsFile(final Context context, final OutputStream outputStream, final String fileName, final Uri uri) {
|
|
||||||
this.context = context;
|
|
||||||
this.outputStream = outputStream;
|
|
||||||
this.fileName = fileName;
|
|
||||||
this.uri = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutputStream getOutputStream() { return outputStream; }
|
|
||||||
public String getFileName() { return fileName; }
|
|
||||||
|
|
||||||
public void delete() {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
|
||||||
context.getContentResolver().delete(uri, null, null);
|
|
||||||
else
|
|
||||||
new File(fileName).delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DownloadsFile save(final Context context, final String name, final String mimeType, final boolean overwriteExisting) throws Exception {
|
public static DownloadsFile save(final Context context, final String name, final String mimeType, final boolean overwriteExisting) throws Exception {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
final ContentResolver contentResolver = context.getContentResolver();
|
final ContentResolver contentResolver = context.getContentResolver();
|
||||||
@ -89,12 +65,40 @@ public class DownloadsFileSaver {
|
|||||||
}
|
}
|
||||||
return new DownloadsFile(context, contentStream, path, contentUri);
|
return new DownloadsFile(context, contentStream, path, contentUri);
|
||||||
} else {
|
} else {
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation") final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
|
||||||
final File file = new File(path, name);
|
final File file = new File(path, name);
|
||||||
if (!path.isDirectory() && !path.mkdirs())
|
if (!path.isDirectory() && !path.mkdirs())
|
||||||
throw new IOException(context.getString(R.string.create_output_dir_error));
|
throw new IOException(context.getString(R.string.create_output_dir_error));
|
||||||
return new DownloadsFile(context, new FileOutputStream(file), file.getAbsolutePath(), null);
|
return new DownloadsFile(context, new FileOutputStream(file), file.getAbsolutePath(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DownloadsFile {
|
||||||
|
private Context context;
|
||||||
|
private String fileName;
|
||||||
|
private OutputStream outputStream;
|
||||||
|
private Uri uri;
|
||||||
|
|
||||||
|
private DownloadsFile(final Context context, final OutputStream outputStream, final String fileName, final Uri uri) {
|
||||||
|
this.context = context;
|
||||||
|
this.outputStream = outputStream;
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||||
|
context.getContentResolver().delete(uri, null, null);
|
||||||
|
else
|
||||||
|
new File(fileName).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getOutputStream() {
|
||||||
|
return outputStream;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,14 +51,6 @@ public final class ErrorMessages {
|
|||||||
BackendException.Reason.TUN_CREATION_ERROR, R.string.tun_create_error,
|
BackendException.Reason.TUN_CREATION_ERROR, R.string.tun_create_error,
|
||||||
BackendException.Reason.GO_ACTIVATION_ERROR_CODE, R.string.tunnel_on_error
|
BackendException.Reason.GO_ACTIVATION_ERROR_CODE, R.string.tunnel_on_error
|
||||||
));
|
));
|
||||||
private static final Map<RootShellException.Reason, Integer> RSE_REASON_MAP = new EnumMap<>(Maps.of(
|
|
||||||
RootShellException.Reason.NO_ROOT_ACCESS, R.string.error_root,
|
|
||||||
RootShellException.Reason.SHELL_MARKER_COUNT_ERROR, R.string.shell_marker_count_error,
|
|
||||||
RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR, R.string.shell_exit_status_read_error,
|
|
||||||
RootShellException.Reason.SHELL_START_ERROR, R.string.shell_start_error,
|
|
||||||
RootShellException.Reason.CREATE_BIN_DIR_ERROR, R.string.create_bin_dir_error,
|
|
||||||
RootShellException.Reason.CREATE_TEMP_DIR_ERROR, R.string.create_temp_dir_error
|
|
||||||
));
|
|
||||||
private static final Map<Format, Integer> KFE_FORMAT_MAP = new EnumMap<>(Maps.of(
|
private static final Map<Format, Integer> KFE_FORMAT_MAP = new EnumMap<>(Maps.of(
|
||||||
Format.BASE64, R.string.key_length_explanation_base64,
|
Format.BASE64, R.string.key_length_explanation_base64,
|
||||||
Format.BINARY, R.string.key_length_explanation_binary,
|
Format.BINARY, R.string.key_length_explanation_binary,
|
||||||
@ -74,6 +66,14 @@ public final class ErrorMessages {
|
|||||||
InetNetwork.class, R.string.parse_error_inet_network,
|
InetNetwork.class, R.string.parse_error_inet_network,
|
||||||
Integer.class, R.string.parse_error_integer
|
Integer.class, R.string.parse_error_integer
|
||||||
);
|
);
|
||||||
|
private static final Map<RootShellException.Reason, Integer> RSE_REASON_MAP = new EnumMap<>(Maps.of(
|
||||||
|
RootShellException.Reason.NO_ROOT_ACCESS, R.string.error_root,
|
||||||
|
RootShellException.Reason.SHELL_MARKER_COUNT_ERROR, R.string.shell_marker_count_error,
|
||||||
|
RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR, R.string.shell_exit_status_read_error,
|
||||||
|
RootShellException.Reason.SHELL_START_ERROR, R.string.shell_start_error,
|
||||||
|
RootShellException.Reason.CREATE_BIN_DIR_ERROR, R.string.create_bin_dir_error,
|
||||||
|
RootShellException.Reason.CREATE_TEMP_DIR_ERROR, R.string.create_temp_dir_error
|
||||||
|
));
|
||||||
|
|
||||||
private ErrorMessages() {
|
private ErrorMessages() {
|
||||||
// Prevent instantiation
|
// Prevent instantiation
|
||||||
|
Loading…
Reference in New Issue
Block a user