aboutsummaryrefslogtreecommitdiff
path: root/vendor/libadb-android/libadb/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/libadb-android/libadb/src/main')
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java509
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbAuthenticationFailedException.java14
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java550
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java5
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbPairingRequiredException.java7
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java114
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java199
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java159
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/ByteArrayNoThrowOutputStream.java24
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java9
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/LocalServices.java271
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PRNGFixes.java319
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java28
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java151
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java91
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/StringCompat.java26
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AdbMdns.java193
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AndroidUtils.java58
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/package.html1
19 files changed, 491 insertions, 2237 deletions
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java
deleted file mode 100644
index 8452a86..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java
+++ /dev/null
@@ -1,509 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
-
-package io.github.muntashirakon.adb;
-
-import android.content.Context;
-import android.os.Build;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.WorkerThread;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.security.auth.DestroyFailedException;
-
-import io.github.muntashirakon.adb.android.AdbMdns;
-
-@SuppressWarnings("unused")
-public abstract class AbsAdbConnectionManager implements Closeable {
- private final Object mLock = new Object();
- @Nullable
- private AdbConnection mAdbConnection;
- private String mHostAddress = "127.0.0.1";
- private int mApi = Build.VERSION_CODES.BASE;
- private long mTimeout = Long.MAX_VALUE;
- private TimeUnit mTimeoutUnit = TimeUnit.MILLISECONDS;
- private boolean mThrowOnUnauthorised = false;
-
- /**
- * Return generated/stored private key.
- */
- @NonNull
- protected abstract PrivateKey getPrivateKey();
-
- /**
- * Return public key wrapped around a certificate.
- */
- @NonNull
- protected abstract Certificate getCertificate();
-
- /**
- * Return a name for the device. This can be the app label, hostname or user@hostname.
- */
- @NonNull
- protected abstract String getDeviceName();
-
- /**
- * Set host address for this connection. On the same device, this should be {@code 127.0.0.1}.
- */
- @CallSuper
- public void setHostAddress(@NonNull String hostAddress) {
- mHostAddress = Objects.requireNonNull(hostAddress);
- }
-
- /**
- * Get host address for this connection. Default value is {@code 127.0.0.1}.
- */
- @NonNull
- public String getHostAddress() {
- return mHostAddress;
- }
-
- /**
- * Set Android API (i.e. SDK) version for this connection. If the daemon and the client are located in the same
- * directory, the value should be {@link Build.VERSION#SDK_INT} in order to improve performance as well as security.
- *
- * @param api The API version, default is {@link Build.VERSION_CODES#BASE}.
- */
- public void setApi(int api) {
- this.mApi = api;
- }
-
- /**
- * Get Android API (i.e. SDK) version for this connection. Default value is {@link Build.VERSION_CODES#BASE}.
- */
- public int getApi() {
- return mApi;
- }
-
- /**
- * Set time to wait for the connection to be made.
- *
- * @param timeout Timeout value
- * @param unit Timeout unit
- */
- @CallSuper
- public void setTimeout(long timeout, TimeUnit unit) {
- mTimeout = timeout;
- mTimeoutUnit = unit;
- }
-
- /**
- * Get time to wait for the connection to be made. If not set using {@link #setTimeout(long, TimeUnit)}, the default
- * timeout is {@link Long#MAX_VALUE} milliseconds.
- *
- * @return Timeout in milliseconds
- */
- public long getTimeout() {
- return mTimeoutUnit.toMillis(mTimeout);
- }
-
- /**
- * Get the unit for the timeout. If not set using {@link #setTimeout(long, TimeUnit)}, the default timeout unit is
- * {@link TimeUnit#MILLISECONDS}.
- */
- @NonNull
- public TimeUnit getTimeoutUnit() {
- return mTimeoutUnit;
- }
-
- /**
- * Set whether to throw {@link AdbAuthenticationFailedException} if the daemon rejects the first authentication
- * attempt.
- *
- * @param throwOnUnauthorised {@code true} to throw {@link AdbAuthenticationFailedException} or {@code false}
- * otherwise.
- */
- @CallSuper
- public void setThrowOnUnauthorised(boolean throwOnUnauthorised) {
- mThrowOnUnauthorised = throwOnUnauthorised;
- }
-
- /**
- * Get whether to throw {@link AdbAuthenticationFailedException} if the daemon rejects the first authentication
- * attempt.
- *
- * @return {@code true} if the system is configured to throw {@link AdbAuthenticationFailedException} or
- * {@code false} otherwise. The default value is {@code false}.
- */
- public boolean isThrowOnUnauthorised() {
- return mThrowOnUnauthorised;
- }
-
- /**
- * Get the {@link AdbConnection} backed by this object.
- *
- * @return Underlying {@link AdbConnection}, or {@code null} if the connection hasn't been made yet.
- */
- @CallSuper
- @Nullable
- public AdbConnection getAdbConnection() {
- synchronized (mLock) {
- return mAdbConnection;
- }
- }
-
- /**
- * Check if it is connected to an ADB daemon.
- *
- * @return {@code true} if connected, {@code false} otherwise.
- */
- public boolean isConnected() {
- synchronized (mLock) {
- return mAdbConnection != null && mAdbConnection.isConnected() && mAdbConnection.isConnectionEstablished();
- }
- }
-
- /**
- * Attempt to connect to ADB by performing an automatic network discovery of TLS host and port. Host address set by
- * {@link #setHostAddress(String)} is ignored.
- *
- * @param context Application context
- * @param timeoutMillis Amount of time spent in searching for a host and a port.
- * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
- * attempt is unsuccessful, or it has already been made.
- * @throws IOException If the socket connection could not be made.
- * @throws InterruptedException If timeout has reached.
- * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB
- * daemon has rejected the first authentication attempt, which indicates
- * that the daemon has not saved the public key from a previous connection.
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- @WorkerThread
- @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
- public boolean connectTls(@NonNull Context context, long timeoutMillis)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- return autoConnect(context, AdbMdns.SERVICE_TYPE_TLS_CONNECT, timeoutMillis);
- }
-
- /**
- * Attempt to connect to ADB by performing an automatic network discovery of TCP host and port. Host address set by
- * {@link #setHostAddress(String)} is ignored.
- *
- * @param context Application context
- * @param timeoutMillis Amount of time spent in searching for a host and a port.
- * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
- * attempt is unsuccessful, or it has already been made.
- * @throws IOException If the socket connection could not be made.
- * @throws InterruptedException If timeout has reached.
- * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB
- * daemon has rejected the first authentication attempt, which indicates
- * that the daemon has not saved the public key from a previous connection.
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- @WorkerThread
- @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
- public boolean connectTcp(@NonNull Context context, long timeoutMillis)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- return autoConnect(context, AdbMdns.SERVICE_TYPE_ADB, timeoutMillis);
- }
-
- /**
- * Attempt to connect to ADB by performing an automatic network discovery of host and port. Host address set by
- * {@link #setHostAddress(String)} is ignored.
- *
- * @param context Application context
- * @param timeoutMillis Amount of time spent in searching for a host and a port.
- * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
- * attempt is unsuccessful, or it has already been made.
- * @throws IOException If the socket connection could not be made.
- * @throws InterruptedException If timeout has reached.
- * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB
- * daemon has rejected the first authentication attempt, which indicates
- * that the daemon has not saved the public key from a previous connection.
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- @WorkerThread
- @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
- public boolean autoConnect(@NonNull Context context, long timeoutMillis)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- synchronized (mLock) {
- AtomicInteger atomicPort = new AtomicInteger(-1);
- AtomicReference<String> atomicHostAddress = new AtomicReference<>(null);
- CountDownLatch resolveHostAndPort = new CountDownLatch(1);
-
- AdbMdns adbMdnsTcp = new AdbMdns(context, AdbMdns.SERVICE_TYPE_ADB, (hostAddress, port) -> {
- if (hostAddress != null) {
- atomicHostAddress.set(hostAddress.getHostAddress());
- atomicPort.set(port);
- }
- resolveHostAndPort.countDown();
- });
- adbMdnsTcp.start();
-
- AdbMdns adbMdnsTls = new AdbMdns(context, AdbMdns.SERVICE_TYPE_TLS_CONNECT, (hostAddress, port) -> {
- if (hostAddress != null) {
- atomicHostAddress.set(hostAddress.getHostAddress());
- atomicPort.set(port);
- }
- resolveHostAndPort.countDown();
- });
- adbMdnsTls.start();
-
- try {
- if (!resolveHostAndPort.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
- throw new InterruptedException("Timed out while trying to find a valid host address and port");
- }
- } finally {
- adbMdnsTcp.stop();
- adbMdnsTls.stop();
- }
-
- String host = atomicHostAddress.get();
- int port = atomicPort.get();
-
- if (host == null || port == -1) {
- throw new IOException("Could not find any valid host address or port");
- }
-
- mHostAddress = host;
- mAdbConnection = new AdbConnection.Builder(host, port)
- .setApi(mApi)
- .setKeyPair(getAdbKeyPair())
- .setDeviceName(Objects.requireNonNull(getDeviceName()))
- .build();
- return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised);
- }
- }
-
- @WorkerThread
- @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
- private boolean autoConnect(@NonNull Context context, @AdbMdns.ServiceType @NonNull String serviceType, long timeoutMillis)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- synchronized (mLock) {
- AtomicInteger atomicPort = new AtomicInteger(-1);
- AtomicReference<String> atomicHostAddress = new AtomicReference<>(null);
- CountDownLatch resolveHostAndPort = new CountDownLatch(1);
-
- AdbMdns adbMdns = new AdbMdns(context, serviceType, (hostAddress, port) -> {
- if (hostAddress != null) {
- atomicHostAddress.set(hostAddress.getHostAddress());
- atomicPort.set(port);
- }
- resolveHostAndPort.countDown();
- });
- adbMdns.start();
-
- try {
- if (!resolveHostAndPort.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
- throw new InterruptedException("Timed out while trying to find a valid host address and port");
- }
- } finally {
- adbMdns.stop();
- }
-
- String host = atomicHostAddress.get();
- int port = atomicPort.get();
-
- if (host == null || port == -1) {
- throw new IOException("Could not find any valid host address or port");
- }
-
- mHostAddress = host;
- mAdbConnection = new AdbConnection.Builder(host, port)
- .setApi(mApi)
- .setKeyPair(getAdbKeyPair())
- .setDeviceName(Objects.requireNonNull(getDeviceName()))
- .build();
- return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised);
- }
- }
-
- /**
- * Attempt to connect to ADB given a port number. Host address is set via {@link #setHostAddress(String)}.
- *
- * @param port Port number
- * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
- * attempt is unsuccessful, or it has already been made.
- * @throws IOException If the socket connection could not be made.
- * @throws InterruptedException If timeout has reached.
- * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB
- * daemon has rejected the first authentication attempt, which indicates
- * that the daemon has not saved the public key from a previous connection.
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- @WorkerThread
- public boolean connect(int port) throws IOException, InterruptedException, AdbPairingRequiredException {
- synchronized (mLock) {
- if (isConnected()) {
- return false;
- }
- mAdbConnection = new AdbConnection.Builder(mHostAddress, port)
- .setApi(mApi)
- .setKeyPair(getAdbKeyPair())
- .setDeviceName(Objects.requireNonNull(getDeviceName()))
- .build();
- return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised);
- }
- }
-
- /**
- * Attempt to connect to ADB via a host address and a port number.
- *
- * @param host Host address to use instead of taking it from the {@link #getHostAddress()}
- * @param port Port number
- * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
- * attempt is unsuccessful, or it has already been made.
- * @throws IOException If the socket connection could not be made.
- * @throws InterruptedException If timeout has reached.
- * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the
- * ADB daemon has rejected the first authentication attempt, which
- * indicates that the daemon has not saved the public key from a previous
- * connection.
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- @WorkerThread
- public boolean connect(@NonNull String host, int port)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- synchronized (mLock) {
- if (isConnected()) {
- return false;
- }
- mHostAddress = host;
- mAdbConnection = new AdbConnection.Builder(host, port)
- .setApi(mApi)
- .setKeyPair(getAdbKeyPair())
- .setDeviceName(Objects.requireNonNull(getDeviceName()))
- .build();
- return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised);
- }
- }
-
- /**
- * Disconnect the underlying {@link AdbConnection}.
- *
- * @throws IOException If the underlying socket fails to close
- */
- public void disconnect() throws IOException {
- synchronized (mLock) {
- if (mAdbConnection != null) {
- mAdbConnection.close();
- mAdbConnection = null;
- }
- }
- }
-
- /**
- * Opens an {@link AdbStream} object corresponding to the specified destination.
- * This routine will block until the connection completes.
- *
- * @param destination The destination to open on the target
- * @return {@link AdbStream} object corresponding to the specified destination
- * @throws IOException If the steam fails or no connection has been made
- * @throws InterruptedException If the stream fails while sending the packet
- * @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8.
- */
- @WorkerThread
- @NonNull
- public AdbStream openStream(String destination) throws IOException, InterruptedException {
- synchronized (mLock) {
- if (mAdbConnection != null && mAdbConnection.isConnected()) {
- try {
- return mAdbConnection.open(destination, mTimeout, mTimeoutUnit);
- } catch (AdbPairingRequiredException e) {
- throw new IllegalStateException(e);
- }
- }
- throw new IOException("Not connected to ADB.");
- }
- }
-
- /**
- * Opens an {@link AdbStream} object corresponding to the specified destination.
- * This routine will block until the connection completes.
- *
- * @param service The service to open. One of the services under {@link LocalServices.Services}.
- * @param args Additional arguments supported by the service (see the corresponding constant to learn more).
- * @return AdbStream object corresponding to the specified destination
- * @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
- * @throws IOException If the stream fails while sending the packet
- * @throws InterruptedException If we are unable to wait for the connection to finish
- */
- @NonNull
- public AdbStream openStream(@LocalServices.Services int service, @NonNull String... args)
- throws IOException, InterruptedException {
- synchronized (mLock) {
- if (mAdbConnection != null && mAdbConnection.isConnected()) {
- try {
- return mAdbConnection.open(LocalServices.getDestination(service, args),
- mTimeout, mTimeoutUnit);
- } catch (AdbPairingRequiredException e) {
- throw new IllegalStateException(e);
- }
- }
- throw new IOException("Not connected to ADB.");
- }
- }
-
- /**
- * Pair with an ADB daemon given port number and pairing code.
- *
- * @param port Port number
- * @param pairingCode The six-digit pairing code as string
- * @return {@code true} if the pairing is successful and {@code false} otherwise.
- * @throws Exception If pairing failed for some reason.
- */
- @WorkerThread
- @RequiresApi(Build.VERSION_CODES.GINGERBREAD)
- public boolean pair(int port, @NonNull String pairingCode) throws Exception {
- return pair(mHostAddress, port, pairingCode);
- }
-
- /**
- * Pair with an ADB daemon given host address, port number and pairing code.
- *
- * @param host Host address to use instead of taking it from the {@link #getHostAddress()}
- * @param port Port number
- * @param pairingCode The six-digit pairing code as string
- * @return {@code true} if the pairing is successful and {@code false} otherwise.
- * @throws Exception If pairing failed for some reason.
- */
- @WorkerThread
- @RequiresApi(Build.VERSION_CODES.GINGERBREAD)
- public boolean pair(@NonNull String host, int port, @NonNull String pairingCode) throws Exception {
- synchronized (mLock) {
- KeyPair keyPair = getAdbKeyPair();
- try (PairingConnectionCtx pairingClient = new PairingConnectionCtx(Objects.requireNonNull(host), port,
- StringCompat.getBytes(Objects.requireNonNull(pairingCode), "UTF-8"), keyPair, getDeviceName())) {
- // TODO: 5/12/21 Return true/false instead of only exceptions
- pairingClient.start();
- }
- return true;
- }
- }
-
- /**
- * Close the underlying {@link AdbConnection} and destroy the private key.
- *
- * @throws IOException If socket fails to close.
- */
- @Override
- public void close() throws IOException {
- try {
- getPrivateKey().destroy();
- } catch (DestroyFailedException | NoSuchMethodError e) {
- e.printStackTrace();
- }
- if (mAdbConnection != null) {
- mAdbConnection.close();
- mAdbConnection = null;
- }
- }
-
- @NonNull
- private KeyPair getAdbKeyPair() {
- return new KeyPair(Objects.requireNonNull(getPrivateKey()), Objects.requireNonNull(getCertificate()));
- }
-}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbAuthenticationFailedException.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbAuthenticationFailedException.java
deleted file mode 100644
index bf50eeb..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbAuthenticationFailedException.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: BSD-3-Clause AND (GPL-3.0-or-later OR Apache-2.0)
-
-package io.github.muntashirakon.adb;
-
-/**
- * Thrown when the ADB daemon rejects our initial authentication attempt, which typically means that the peer has not
- * previously saved our public key.
- */
-// Copyright 2020 Sam Palmer
-public class AdbAuthenticationFailedException extends RuntimeException {
- public AdbAuthenticationFailedException() {
- super("Initial authentication attempt rejected by peer.");
- }
-}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java
index 7674adc..2abcb72 100644
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java
@@ -14,27 +14,24 @@ import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
+import java.net.InetSocketAddress;
import java.net.ConnectException;
-import java.net.SocketTimeoutException;
import java.net.Socket;
+import java.net.SocketTimeoutException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
-import java.security.interfaces.RSAPublicKey;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
-import javax.security.auth.DestroyFailedException;
-
/**
* This class represents an ADB connection.
*/
// Copyright 2013 Cameron Gutman
-public class AdbConnection implements Closeable {
- public static final String TAG = AdbConnection.class.getSimpleName();
+public class AdbConnection implements Closeable, AdbStream.Transport {
+ public static final String TAG = "scrcpy-android";
/**
* The underlying socket that this class uses to communicate with the target device.
@@ -47,8 +44,6 @@ public class AdbConnection implements Closeable {
private final int mPort;
- private final int mApi;
-
/**
* The last allocated local stream ID. The ID chosen for the next stream will be this value + 1.
*/
@@ -93,15 +88,8 @@ public class AdbConnection implements Closeable {
*/
private volatile boolean mConnectAttempted;
- /**
- * Whether the connection thread should give up if the first authentication attempt fails.
- */
- private volatile boolean mAbortOnUnauthorised;
-
- /**
- * Whether the first authentication attempt failed and {@link #mAbortOnUnauthorised} was {@code true}.
- */
- private volatile boolean mAuthorisationFailed;
+ /** The worker thread is one-shot, even after a failed connection. */
+ private boolean mConnectionThreadStarted;
/**
* Specifies whether a CNXN packet has been received from the peer.
@@ -122,16 +110,12 @@ public class AdbConnection implements Closeable {
private volatile int mProtocolVersion;
- @NonNull
- private final KeyPair mKeyPair;
+ private final int mLocalMaxData;
- @NonNull
- private volatile String mDeviceName = "Unknown Device";
+ private final int mLocalProtocolVersion;
- /**
- * Specifies whether this connection has already sent a signed token.
- */
- private volatile boolean mSentSignature;
+ @NonNull
+ private final KeyPair mKeyPair;
/**
* A hash map of our opened streams indexed by local ID.
@@ -146,68 +130,49 @@ public class AdbConnection implements Closeable {
private final Object mLock = new Object();
/**
- * Creates a AdbConnection object associated with the socket and crypto object specified.
- *
- * @return A new AdbConnection object.
- * @throws IOException If there is a socket error
+ * Internal constructor to initialize some internal state
*/
- @WorkerThread
- @NonNull
- public static AdbConnection create(@NonNull String host, int port, @NonNull PrivateKey privateKey,
- @NonNull Certificate certificate)
- throws IOException {
- return create(host, port, privateKey, certificate, Build.VERSION_CODES.BASE);
- }
+ // LOCAL PATCH: bound on the TCP connect; see the constructor.
+ private static final int CONNECT_TIMEOUT_MS = 10_000;
+ private static final int CLOSE_TIMEOUT_MS = 5_000;
- /**
- * Creates a AdbConnection object associated with the socket and crypto object specified.
- *
- * @return A new AdbConnection object.
- * @throws IOException If there is a socket error
- */
@WorkerThread
- @NonNull
- public static AdbConnection create(@NonNull String host, int port, @NonNull PrivateKey privateKey,
- @NonNull Certificate certificate, int api)
+ private AdbConnection(@NonNull String host, int port, @NonNull KeyPair keyPair)
throws IOException {
- return create(host, port, new KeyPair(Objects.requireNonNull(privateKey), Objects.requireNonNull(certificate)),
- api);
- }
-
- /**
- * Creates a AdbConnection object associated with the socket and crypto object specified.
- *
- * @return A new AdbConnection object.
- * @throws IOException If there is a socket error
- */
- @WorkerThread
- @NonNull
- static AdbConnection create(@NonNull String host, int port, @NonNull KeyPair keyPair, int api) throws IOException {
- return new AdbConnection(host, port, keyPair, api);
- }
-
- /**
- * Internal constructor to initialize some internal state
- */
- @WorkerThread
- private AdbConnection(@NonNull String host, int port, @NonNull KeyPair keyPair, int api) throws IOException {
this.mHost = Objects.requireNonNull(host);
this.mPort = port;
- this.mApi = api;
- this.mProtocolVersion = AdbProtocol.getProtocolVersion(mApi);
- this.mMaxData = AdbProtocol.getMaxData(api);
+ this.mLocalProtocolVersion = AdbProtocol.getProtocolVersion(Build.VERSION_CODES.R);
+ this.mLocalMaxData = AdbProtocol.getMaxData(Build.VERSION_CODES.R);
+ this.mProtocolVersion = mLocalProtocolVersion;
+ this.mMaxData = mLocalMaxData;
this.mKeyPair = Objects.requireNonNull(keyPair);
+ Socket socket = new Socket();
try {
- this.mSocket = new Socket(host, port);
- } catch (Throwable th) {
+ // LOCAL PATCH: was `new Socket(host, port)`, which blocks on
+ // the OS default TCP timeout - minutes on a mobile network -
+ // with no way to cancel. A target that is off or on another
+ // network is the common case, not an edge case, so bound it.
+ socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT_MS);
+ } catch (IOException e) {
+ try { socket.close(); } catch (IOException ignored) {}
//noinspection UnnecessaryInitCause
- throw (IOException) new IOException().initCause(th);
+ throw (IOException) new IOException("cannot reach " + host + ":" + port
+ + " within " + CONNECT_TIMEOUT_MS + " ms").initCause(e);
}
- this.mPlainInputStream = mSocket.getInputStream();
- this.mPlainOutputStream = mSocket.getOutputStream();
-
- // Disable Nagle because we're sending tiny packets
- mSocket.setTcpNoDelay(true);
+ InputStream plainInput;
+ OutputStream plainOutput;
+ try {
+ // Disable Nagle because we're sending tiny packets.
+ socket.setTcpNoDelay(true);
+ plainInput = socket.getInputStream();
+ plainOutput = socket.getOutputStream();
+ } catch (IOException e) {
+ try { socket.close(); } catch (IOException ignored) {}
+ throw (IOException) new IOException("cannot initialize ADB socket").initCause(e);
+ }
+ this.mSocket = socket;
+ this.mPlainInputStream = plainInput;
+ this.mPlainOutputStream = plainOutput;
this.mOpenedStreams = new ConcurrentHashMap<>();
this.mLastLocalId = 0;
@@ -234,7 +199,6 @@ public class AdbConnection implements Closeable {
@NonNull
private Thread createConnectionThread() {
return new Thread(() -> {
- loop:
while (!mConnectionThread.isInterrupted()) {
try {
// Read and parse a message off the socket's input stream
@@ -253,6 +217,9 @@ public class AdbConnection implements Closeable {
// Get the stream object corresponding to the packet
AdbStream waitingStream = mOpenedStreams.get(msg.arg1);
if (waitingStream == null) {
+ if (msg.command == AdbProtocol.A_WRTE) {
+ throw new IOException("WRTE for unknown local stream " + msg.arg1);
+ }
continue;
}
@@ -261,16 +228,16 @@ public class AdbConnection implements Closeable {
// We're ready for writes
waitingStream.updateRemoteId(msg.arg0);
waitingStream.readyForWrite();
-
- // Notify an open/write
- waitingStream.notify();
} else if (msg.command == AdbProtocol.A_WRTE) {
+ if (!waitingStream.hasRemoteId(msg.arg0)) {
+ throw new IOException("WRTE remote stream ID mismatch");
+ }
// Got some data from our partner
waitingStream.addPayload(msg.payload);
-
- // Tell it we're ready for more
- waitingStream.sendReady();
} else { // if (msg.command == AdbProtocol.A_CLSE) {
+ if (!waitingStream.hasRemoteId(msg.arg0) && msg.arg0 != 0) {
+ throw new IOException("CLSE remote stream ID mismatch");
+ }
mOpenedStreams.remove(msg.arg1);
// Notify readers and writers
waitingStream.notifyClose(true);
@@ -279,71 +246,57 @@ public class AdbConnection implements Closeable {
break;
}
case AdbProtocol.A_STLS: {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
- sendPacket(AdbProtocol.generateStls());
-
- SSLContext sslContext = SslUtils.getSslContext(mKeyPair);
- SSLSocket tlsSocket = (SSLSocket) sslContext.getSocketFactory()
- .createSocket(mSocket, mHost, mPort, true);
- tlsSocket.startHandshake();
- Log.d(TAG, "Handshake succeeded.");
-
- synchronized (AdbConnection.this) {
- mTlsInputStream = tlsSocket.getInputStream();
- mTlsOutputStream = tlsSocket.getOutputStream();
- mIsTls = true;
- }
+ if (mIsTls || mConnectionEstablished
+ || msg.arg0 < AdbProtocol.A_STLS_VERSION_MIN) {
+ throw new IOException("Invalid or repeated STLS request");
}
- break;
- }
- case AdbProtocol.A_AUTH: {
- if (mIsTls) {
- break;
- }
- if (msg.arg0 != AdbProtocol.ADB_AUTH_TOKEN) {
- break;
- }
- byte[] packet;
- // This is an authentication challenge
- if (mSentSignature) {
- if (mAbortOnUnauthorised) {
- mAuthorisationFailed = true;
- break loop;
- }
+ sendPacket(AdbProtocol.generateStls());
- // We've already tried our signature, so send our public key
- packet = AdbProtocol.generateAuth(AdbProtocol.ADB_AUTH_RSAPUBLICKEY, AndroidPubkey
- .encodeWithName((RSAPublicKey) mKeyPair.getPublicKey(), mDeviceName));
- } else {
- // Sign the token
- packet = AdbProtocol.generateAuth(AdbProtocol.ADB_AUTH_SIGNATURE, AndroidPubkey
- .adbAuthSign(mKeyPair.getPrivateKey(), msg.payload));
- mSentSignature = true;
- }
+ SSLContext sslContext = SslUtils.getSslContext(mKeyPair);
+ SSLSocket tlsSocket = (SSLSocket) sslContext.getSocketFactory()
+ .createSocket(mSocket, mHost, mPort, true);
+ tlsSocket.startHandshake();
+ Log.d(TAG, "Handshake succeeded.");
- // Write the AUTH reply
- sendPacket(packet);
+ synchronized (AdbConnection.this) {
+ mTlsInputStream = tlsSocket.getInputStream();
+ mTlsOutputStream = tlsSocket.getOutputStream();
+ mIsTls = true;
+ }
break;
}
+ case AdbProtocol.A_AUTH: {
+ throw new IOException("ADB peer requested legacy authentication without TLS");
+ }
case AdbProtocol.A_CNXN: {
+ if (!mIsTls) {
+ throw new IOException("ADB peer did not negotiate TLS");
+ }
+ if (msg.arg0 < AdbProtocol.A_VERSION_MIN || msg.arg1 <= 0) {
+ throw new IOException("Invalid CNXN parameters: version="
+ + msg.arg0 + " maxData=" + msg.arg1);
+ }
synchronized (AdbConnection.this) {
- mProtocolVersion = msg.arg0;
- mMaxData = msg.arg1;
+ // Both fields come from an unauthenticated
+ // peer. Negotiate down, never let the peer
+ // raise our allocation or checksum limits.
+ mProtocolVersion = Math.min(msg.arg0, mLocalProtocolVersion);
+ mMaxData = Math.min(msg.arg1, mLocalMaxData);
mConnectionEstablished = true;
AdbConnection.this.notifyAll();
}
break;
}
case AdbProtocol.A_OPEN:
- case AdbProtocol.A_SYNC:
default:
- Log.e(TAG, String.format("Unrecognized command = 0x%x", msg.command));
- // Unrecognized packet, just drop it
- break;
+ throw new IOException(String.format(
+ "Unexpected ADB command 0x%x", msg.command));
}
} catch (Exception e) {
- mConnectionException = e;
- e.printStackTrace();
+ if (!mSocket.isClosed()) {
+ mConnectionException = e;
+ Log.e(TAG, "ADB connection failed", e);
+ }
// The cleanup is taken care of by a combination of this thread and close()
break;
}
@@ -360,48 +313,6 @@ public class AdbConnection implements Closeable {
}
/**
- * Set a name for the device. Default is “Unknown Device”.
- *
- * @param deviceName Name of the device, could be the app label, hostname or user@hostname.
- */
- public void setDeviceName(@NonNull String deviceName) {
- this.mDeviceName = Objects.requireNonNull(deviceName);
- }
-
- /**
- * Get the version of the ADB protocol supported by the ADB daemon. The result may depend on the API version
- * specified and whether the connection has been established. In API 29 (Android 9) or later, the daemon returns
- * {@link AdbProtocol#A_VERSION_SKIP_CHECKSUM} regardless of the protocol used to create the connection. So, if
- * {@link #mApi} is set to API 28 or earlier but the OS version is Android 9 or later, before establishing the
- * connection, it returns {@link AdbProtocol#A_VERSION_MIN}, and after establishing the connection, it returns
- * {@link AdbProtocol#A_VERSION_SKIP_CHECKSUM}. In other cases, it always returns {@link AdbProtocol#A_VERSION_MIN}.
- *
- * @see #isConnectionEstablished()
- */
- public int getProtocolVersion() {
- return mProtocolVersion;
- }
-
- /**
- * Get the max data size supported by the ADB daemon. A connection have to be attempted before calling this method
- * and shall be blocked if the connection is in progress.
- *
- * @return The maximum data size indicated in the CONNECT packet.
- * @throws InterruptedException If a connection cannot be waited on.
- * @throws IOException if the connection fails.
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- public int getMaxData() throws InterruptedException, IOException, AdbPairingRequiredException {
- if (!mConnectAttempted) {
- throw new IllegalStateException("connect() must be called first");
- }
-
- waitForConnection(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
-
- return mMaxData;
- }
-
- /**
* Whether a connection has been established. A connection has been established if a CONNECT request has been
* received from the ADB daemon.
*/
@@ -409,101 +320,43 @@ public class AdbConnection implements Closeable {
return mConnectionEstablished;
}
- /**
- * Whether the underlying socket is connected to an ADB daemon and is not in a closed state.
- */
- public boolean isConnected() {
- return !mSocket.isClosed() && mSocket.isConnected();
- }
-
- /**
- * Same as {@link #connect(long, TimeUnit, boolean)} without throwing anything if the first authentication attempt
- * fails.
- *
- * @return {@code true} if the connection was established, or {@code false} if the connection timed out
- * @throws IOException If the socket fails while connecting
- * @throws InterruptedException If timeout has reached
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- public boolean connect() throws IOException, InterruptedException, AdbPairingRequiredException {
- return connect(Long.MAX_VALUE, TimeUnit.MILLISECONDS, false);
+ @Override
+ public int getMaxData() {
+ return mMaxData;
}
/**
* Connects to the remote device. This routine will block until the connection completes or the timeout elapses.
*
- * @param timeout the time to wait for the lock
- * @param unit the time unit of the timeout argument
- * @param throwOnUnauthorised Whether to throw an {@link AdbAuthenticationFailedException}
- * if the peer rejects out first authentication attempt
+ * @param timeout the time to wait for the lock
+ * @param unit the time unit of the timeout argument
* @return {@code true} if the connection was established, or {@code false} if the connection timed out
* @throws IOException If the socket fails while connecting
* @throws InterruptedException If timeout has reached
- * @throws AdbAuthenticationFailedException If {@code throwOnUnauthorised} is {@code true} and the peer rejects the
- * first authentication attempt, which indicates that the peer has not
- * saved the public key from a previous connection
- * @throws AdbPairingRequiredException If ADB lacks pairing
*/
- public boolean connect(long timeout, @NonNull TimeUnit unit, boolean throwOnUnauthorised)
- throws IOException, InterruptedException, AdbAuthenticationFailedException, AdbPairingRequiredException {
- if (mConnectionEstablished) {
- throw new IllegalStateException("Already connected");
+ public synchronized boolean connect(long timeout, @NonNull TimeUnit unit)
+ throws IOException, InterruptedException {
+ validateTimeout(timeout, unit);
+ if (mConnectionThreadStarted) {
+ throw new IllegalStateException("Connection already attempted");
}
// Send CONNECT
- sendPacket(AdbProtocol.generateConnect(mApi));
+ sendPacket(AdbProtocol.generateConnect(Build.VERSION_CODES.R));
// Start the connection thread to respond to the peer
mConnectAttempted = true;
- mAbortOnUnauthorised = throwOnUnauthorised;
- mAuthorisationFailed = false;
+ mConnectionThreadStarted = true;
mConnectionThread.start();
return waitForConnection(timeout, Objects.requireNonNull(unit));
}
- /**
- * Opens an {@link AdbStream} object corresponding to the specified destination.
- * This routine will block until the connection completes.
- *
- * @param service The service to open. One of the services under {@link LocalServices.Services}.
- * @param args Additional arguments supported by the service (see the corresponding constant to learn more).
- * @return AdbStream object corresponding to the specified destination
- * @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
- * @throws IOException If the stream fails while sending the packet
- * @throws InterruptedException If we are unable to wait for the connection to finish
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- @NonNull
- public AdbStream open(@LocalServices.Services int service, @NonNull String... args)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- if (service < LocalServices.SERVICE_FIRST || service > LocalServices.SERVICE_LAST) {
- throw new IllegalArgumentException("Invalid service: " + service);
- }
- return open(LocalServices.getDestination(service, args));
- }
-
- /**
- * Opens an AdbStream object corresponding to the specified destination.
- * This routine will block until the connection completes.
- *
- * @param destination The destination to open on the target
- * @return AdbStream object corresponding to the specified destination
- * @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
- * @throws IOException If the stream fails while sending the packet
- * @throws InterruptedException If we are unable to wait for the connection to finish
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- @NonNull
- public AdbStream open(@NonNull String destination)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- return open(destination, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
- }
-
@NonNull
public AdbStream open(@NonNull String destination, long timeout, @NonNull TimeUnit unit)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- int localId = ++mLastLocalId;
+ throws IOException, InterruptedException {
+ validateTimeout(timeout, unit);
+ Objects.requireNonNull(destination);
if (!mConnectAttempted) {
throw new IllegalStateException("connect() must be called first");
@@ -513,24 +366,26 @@ public class AdbConnection implements Closeable {
throw new SocketTimeoutException("ADB connection timed out.");
}
+ int localId = nextLocalId();
+
// Add this stream to this list of half-open streams
AdbStream stream = new AdbStream(this, localId);
mOpenedStreams.put(localId, stream);
long timeoutMillis = unit.toMillis(timeout);
- long deadline = timeoutMillis == Long.MAX_VALUE
- ? Long.MAX_VALUE : System.currentTimeMillis() + timeoutMillis;
+ long started = System.nanoTime();
try {
// Send OPEN only after publishing the half-open stream so an
// immediate response cannot race past the lookup table.
- sendPacket(AdbProtocol.generateOpen(localId, Objects.requireNonNull(destination)));
+ sendPacket(AdbProtocol.generateOpen(localId, destination));
synchronized (stream) {
while (!stream.isOpen() && !stream.isClosed()) {
- if (deadline == Long.MAX_VALUE) {
+ if (timeoutMillis == Long.MAX_VALUE) {
stream.wait();
continue;
}
- long remaining = deadline - System.currentTimeMillis();
+ long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - started);
+ long remaining = timeoutMillis - elapsed;
if (remaining <= 0) {
throw new SocketTimeoutException("ADB stream open timed out: " + destination);
}
@@ -552,32 +407,35 @@ public class AdbConnection implements Closeable {
return stream;
}
+ private synchronized int nextLocalId() throws IOException {
+ if (mLastLocalId == Integer.MAX_VALUE) {
+ throw new IOException("ADB stream ID space exhausted");
+ }
+ return ++mLastLocalId;
+ }
+
private boolean waitForConnection(long timeout, @NonNull TimeUnit unit)
- throws InterruptedException, IOException, AdbPairingRequiredException {
+ throws InterruptedException, IOException {
synchronized (this) {
// Block if a connection is pending, but not yet complete
- long timeoutEndMillis = System.currentTimeMillis() + Objects.requireNonNull(unit).toMillis(timeout);
- while (!mConnectionEstablished && mConnectAttempted && timeoutEndMillis - System.currentTimeMillis() > 0) {
- wait(timeoutEndMillis - System.currentTimeMillis());
+ long timeoutMillis = Objects.requireNonNull(unit).toMillis(timeout);
+ long started = System.nanoTime();
+ while (!mConnectionEstablished && mConnectAttempted) {
+ if (timeoutMillis == Long.MAX_VALUE) {
+ wait();
+ continue;
+ }
+ long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - started);
+ long remainingMillis = timeoutMillis - elapsedMillis;
+ if (remainingMillis <= 0) break;
+ wait(remainingMillis);
}
if (!mConnectionEstablished) {
if (mConnectAttempted) {
return false;
- } else if (mAuthorisationFailed) {
- // The peer may not have saved the public key in the past connections, or they've been removed.
- throw new AdbAuthenticationFailedException();
} else {
- Exception connectionException = mConnectionException;
- if (connectionException != null) {
- if (connectionException instanceof javax.net.ssl.SSLProtocolException) {
- String message = connectionException.getMessage();
- if (message != null && message.contains("protocol error")) {
- throw (AdbPairingRequiredException) (new AdbPairingRequiredException("ADB pairing is required.").initCause(connectionException));
- }
- }
- }
- throw new IOException("Connection failed");
+ throw new IOException("Connection failed", mConnectionException);
}
}
}
@@ -585,16 +443,19 @@ public class AdbConnection implements Closeable {
return true;
}
+ private static void validateTimeout(long timeout, @NonNull TimeUnit unit) {
+ Objects.requireNonNull(unit);
+ if (timeout < 0) throw new IllegalArgumentException("negative timeout");
+ }
+
/**
* This function terminates all I/O on streams associated with this ADB connection
*/
private void cleanupStreams() {
- // Close all streams on this connection
+ // The socket is already unusable. Wake every stream without trying
+ // to write CLSE packets back through the failed transport.
for (AdbStream s : mOpenedStreams.values()) {
- try {
- s.close();
- } catch (IOException ignored) {
- }
+ s.notifyClose(false);
}
mOpenedStreams.clear();
}
@@ -612,18 +473,26 @@ public class AdbConnection implements Closeable {
// Wait for the connection thread to die
mConnectionThread.interrupt();
try {
- mConnectionThread.join();
- } catch (InterruptedException ignored) {
+ if (mConnectionThread != Thread.currentThread()) {
+ mConnectionThread.join(CLOSE_TIMEOUT_MS);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw (IOException) new IOException("interrupted while closing ADB connection")
+ .initCause(e);
}
-
- // Destroy keypair
- try {
- mKeyPair.destroy();
- } catch (DestroyFailedException ignore) {
+ if (mConnectionThread.isAlive()) {
+ throw new IOException("ADB connection thread did not stop within "
+ + CLOSE_TIMEOUT_MS + " ms");
}
+
+ // The connection manager owns the keypair. A disconnect is routine
+ // during reconnect and must not destroy the key reused by the next
+ // connection.
}
- void sendPacket(byte[] packet) throws IOException {
+ @Override
+ public void sendPacket(byte[] packet) throws IOException {
synchronized (mLock) {
OutputStream os = getOutputStream();
os.write(packet);
@@ -631,23 +500,18 @@ public class AdbConnection implements Closeable {
}
}
- void flushPacket() throws IOException {
+ @Override
+ public void flushPacket() throws IOException {
synchronized (mLock) {
getOutputStream().flush();
}
}
public static class Builder {
- private String mHost = "127.0.0.1";
- private int mPort = 5555;
- private int mApi = Build.VERSION_CODES.BASE;
+ private final String mHost;
+ private final int mPort;
private PrivateKey mPrivateKey;
private Certificate mCertificate;
- private KeyPair mKeyPair;
- private String mDeviceName;
-
- public Builder() {
- }
public Builder(String host, int port) {
mHost = host;
@@ -655,44 +519,6 @@ public class AdbConnection implements Closeable {
}
/**
- * Set host address. Default is 127.0.0.1
- */
- public Builder setHost(String host) {
- this.mHost = host;
- return this;
- }
-
- /**
- * Set port number. Default is 5555.
- */
- public Builder setPort(int port) {
- this.mPort = port;
- return this;
- }
-
- /**
- * Set a name for the device. Default is “Unknown Device”.
- *
- * @param deviceName Name of the device, could be the app label, hostname or user@hostname.
- */
- public Builder setDeviceName(String deviceName) {
- this.mDeviceName = deviceName;
- return this;
- }
-
- /**
- * Set Android API (i.e. SDK) version for this connection. If the ADB daemon and the client are located in the
- * same device, the value should be {@link Build.VERSION#SDK_INT} in order to improve performance as well as
- * security.
- *
- * @param api The API version, default is {@link Build.VERSION_CODES#BASE}.
- */
- public Builder setApi(int api) {
- this.mApi = api;
- return this;
- }
-
- /**
* Set generated/stored private key.
*/
public Builder setPrivateKey(PrivateKey privateKey) {
@@ -708,69 +534,19 @@ public class AdbConnection implements Closeable {
return this;
}
- Builder setKeyPair(KeyPair keyPair) {
- this.mKeyPair = keyPair;
- return this;
- }
-
/**
* Creates a new {@link AdbConnection} associated with the socket and crypto object specified.
*
* @throws IOException If there was an error while establishing a socket connection
*/
public AdbConnection build() throws IOException {
- if (mKeyPair == null) {
- if (mPrivateKey == null || mCertificate == null) {
- throw new UnsupportedOperationException("Private key and certificate must be set.");
- }
- mKeyPair = new KeyPair(mPrivateKey, mCertificate);
- }
- AdbConnection adbConnection = create(mHost, mPort, mKeyPair, mApi);
- if (mDeviceName != null) {
- adbConnection.setDeviceName(mDeviceName);
- }
- return adbConnection;
- }
-
- /**
- * Same as {@link #connect(long, TimeUnit, boolean)} without throwing anything if the first authentication
- * attempt fails.
- *
- * @return The underlying {@link AdbConnection}
- * @throws IOException If the socket fails while connecting
- * @throws InterruptedException If timeout has reached
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- public AdbConnection connect() throws IOException, InterruptedException, AdbPairingRequiredException {
- AdbConnection adbConnection = build();
- if (adbConnection.connect()) {
- throw new IOException("Unable to establish a new connection.");
- }
- return adbConnection;
- }
-
- /**
- * Connects to the remote device. This routine will block until the connection completes or the timeout elapses.
- *
- * @param timeout the time to wait for the lock
- * @param unit the time unit of the timeout argument
- * @param throwOnUnauthorised Whether to throw an {@link AdbAuthenticationFailedException}
- * if the peer rejects out first authentication attempt
- * @return {@code true} if the connection was established, or {@code false} if the connection timed out
- * @throws IOException If the socket fails while connecting
- * @throws InterruptedException If timeout has reached
- * @throws AdbAuthenticationFailedException If {@code throwOnUnauthorised} is {@code true} and the peer rejects
- * the first authentication attempt, which indicates that the peer has
- * not saved the public key from a previous connection
- * @throws AdbPairingRequiredException If ADB lacks pairing
- */
- public AdbConnection connect(long timeout, @NonNull TimeUnit unit, boolean throwOnUnauthorised)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- AdbConnection adbConnection = build();
- if (adbConnection.connect(timeout, unit, throwOnUnauthorised)) {
- throw new IOException("Unable to establish a new connection.");
+ if (mHost == null || mHost.isEmpty()) throw new IllegalArgumentException("host is empty");
+ if (mPort < 1 || mPort > 65535) throw new IllegalArgumentException("port is invalid");
+ if (mPrivateKey == null || mCertificate == null) {
+ throw new UnsupportedOperationException("Private key and certificate must be set.");
}
- return adbConnection;
+ return new AdbConnection(mHost, mPort,
+ new KeyPair(mPrivateKey, mCertificate));
}
}
}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java
index 6d0676f..c609e98 100644
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java
@@ -6,7 +6,7 @@ import java.io.IOException;
import java.io.InputStream;
public class AdbInputStream extends InputStream {
- public AdbStream mAdbStream;
+ private final AdbStream mAdbStream;
public AdbInputStream(AdbStream adbStream) {
this.mAdbStream = adbStream;
@@ -18,7 +18,7 @@ public class AdbInputStream extends InputStream {
if (read(bytes) == -1) {
return -1;
}
- return bytes[0];
+ return bytes[0] & 0xff;
}
@Override
@@ -28,7 +28,6 @@ public class AdbInputStream extends InputStream {
@Override
public int read(byte[] b, int off, int len) throws IOException {
- if (mAdbStream.isClosed()) return -1;
return mAdbStream.read(b, off, len);
}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbPairingRequiredException.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbPairingRequiredException.java
deleted file mode 100644
index f324cb0..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbPairingRequiredException.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package io.github.muntashirakon.adb;
-
-public class AdbPairingRequiredException extends Exception {
- public AdbPairingRequiredException(String message) {
- super(message);
- }
-}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java
index 4c58206..79de3b5 100644
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java
@@ -15,6 +15,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
@@ -28,13 +29,6 @@ final class AdbProtocol {
public static final int ADB_HEADER_LENGTH = 24;
/**
- * SYNC(online, sequence, "")
- *
- * @deprecated Obsolete, no longer used. Never used on the client side.
- */
- public static final int A_SYNC = 0x434e5953;
-
- /**
* CNXN is the connect message. No messages (except AUTH) are valid before this message is received.
*/
public static final int A_CNXN = 0x4e584e43;
@@ -42,7 +36,8 @@ final class AdbProtocol {
/**
* The payload sent with the CONNECT message.
*/
- public static final byte[] SYSTEM_IDENTITY_STRING_HOST = StringCompat.getBytes("host::\0", "UTF-8");
+ public static final byte[] SYSTEM_IDENTITY_STRING_HOST =
+ "host::\0".getBytes(StandardCharsets.UTF_8);
/**
* AUTH is the authentication message. It is part of the RSA public key authentication added in Android 4.2.2
@@ -76,7 +71,7 @@ final class AdbProtocol {
public static final int A_STLS = 0x534c5453;
@Retention(RetentionPolicy.SOURCE)
- @IntDef({A_SYNC, A_CNXN, A_OPEN, A_OKAY, A_CLSE, A_WRTE, A_AUTH, A_STLS})
+ @IntDef({A_CNXN, A_OPEN, A_OKAY, A_CLSE, A_WRTE, A_AUTH, A_STLS})
private @interface Command {
}
@@ -93,11 +88,6 @@ final class AdbProtocol {
*/
public static final int MAX_PAYLOAD_V3 = 1024 * 1024;
/**
- * Maximum supported payload size is set to the original to support all APIs
- */
- public static final int MAX_PAYLOAD = MAX_PAYLOAD_V1;
-
- /**
* The original version of the ADB protocol
*/
public static final int A_VERSION_MIN = 0x01000000;
@@ -105,34 +95,12 @@ final class AdbProtocol {
* The new version of the ADB protocol introduced in Android 9 (P) with the introduction of TLS
*/
public static final int A_VERSION_SKIP_CHECKSUM = 0x01000001;
- public static final int A_VERSION = A_VERSION_MIN;
-
/**
* The current version of the Stream-based TLS
*/
public static final int A_STLS_VERSION_MIN = 0x01000000;
public static final int A_STLS_VERSION = A_STLS_VERSION_MIN;
- /**
- * This authentication type represents a SHA1 hash to sign.
- */
- public static final int ADB_AUTH_TOKEN = 1;
-
- /**
- * This authentication type represents the signed SHA1 hash.
- */
- public static final int ADB_AUTH_SIGNATURE = 2;
-
- /**
- * This authentication type represents an RSA public key.
- */
- public static final int ADB_AUTH_RSAPUBLICKEY = 3;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({ADB_AUTH_TOKEN, ADB_AUTH_SIGNATURE, ADB_AUTH_RSAPUBLICKEY})
- private @interface AuthType {
- }
-
public static int getMaxData(int api) {
if (api >= Build.VERSION_CODES.P) {
return MAX_PAYLOAD_V3;
@@ -202,8 +170,14 @@ final class AdbProtocol {
* @return Byte array containing the message
*/
@NonNull
- public static byte[] generateMessage(@Command int command, int arg0, int arg1, @Nullable byte[] data, int offset, int length) {
- // Protocol as defined at https://github.com/aosp-mirror/platform_system_core/blob/6072de17cd812daf238092695f26a552d3122f8c/adb/protocol.txt
+ public static byte[] generateMessage(@Command int command, int arg0, int arg1,
+ @Nullable byte[] data, int offset, int length) {
+ if (length < 0 || offset < 0 || data == null && (offset != 0 || length != 0)
+ || data != null && (offset > data.length || length > data.length - offset)) {
+ throw new IndexOutOfBoundsException(
+ "invalid payload range: offset=" + offset + " length=" + length);
+ }
+ // Protocol: AOSP platform_system_core commit 6072de17, adb/protocol.txt.
// struct message {
// unsigned command; // command identifier constant
// unsigned arg0; // first argument
@@ -256,20 +230,6 @@ final class AdbProtocol {
}
/**
- * Generates an AUTH message with the specified type and payload.
- * <p>
- * AUTH(type, 0, "data")
- *
- * @param type Authentication type (see ADB_AUTH_* constants)
- * @param data The data
- * @return Byte array containing the message
- */
- @NonNull
- public static byte[] generateAuth(@AuthType int type, byte[] data) {
- return generateMessage(A_AUTH, type, 0, data);
- }
-
- /**
* Generates an STLS message with default parameters.
* <p>
* STLS(version, 0, "")
@@ -292,8 +252,9 @@ final class AdbProtocol {
*/
@NonNull
public static byte[] generateOpen(int localId, @NonNull String destination) {
- ByteBuffer bbuf = ByteBuffer.allocate(destination.length() + 1);
- bbuf.put(StringCompat.getBytes(destination, "UTF-8"));
+ byte[] encoded = destination.getBytes(StandardCharsets.UTF_8);
+ ByteBuffer bbuf = ByteBuffer.allocate(encoded.length + 1);
+ bbuf.put(encoded);
bbuf.put((byte) 0);
return generateMessage(A_OPEN, localId, 0, bbuf.array());
}
@@ -389,16 +350,13 @@ final class AdbProtocol {
*/
@NonNull
public static Message parse(@NonNull InputStream in, int protocolVersion, int maxData) throws IOException {
+ if (maxData <= 0 || maxData > MAX_PAYLOAD_V3) {
+ throw new IllegalArgumentException("invalid maximum ADB payload: " + maxData);
+ }
ByteBuffer header = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
// Read header
- int dataRead = 0;
- do {
- int bytesRead = in.read(header.array(), dataRead, ADB_HEADER_LENGTH - dataRead);
- if (bytesRead < 0) {
- throw new IOException("Stream closed");
- } else dataRead += bytesRead;
- } while (dataRead < ADB_HEADER_LENGTH);
+ readFully(in, header.array(), ADB_HEADER_LENGTH);
Message msg = new Message(header);
@@ -406,13 +364,19 @@ final class AdbProtocol {
if (msg.command != (~msg.magic)) { // magic = cmd ^ 0xFFFFFFFF
throw new StreamCorruptedException(String.format("Invalid header: Invalid magic 0x%x.", msg.magic));
}
- if (msg.command != A_SYNC && msg.command != A_CNXN && msg.command != A_OPEN && msg.command != A_OKAY
+ if (msg.command != A_CNXN && msg.command != A_OPEN && msg.command != A_OKAY
&& msg.command != A_CLSE && msg.command != A_WRTE && msg.command != A_AUTH
&& msg.command != A_STLS) {
throw new StreamCorruptedException(String.format("Invalid header: Invalid command 0x%x.", msg.command));
}
if (msg.dataLength < 0 || msg.dataLength > maxData) {
- throw new StreamCorruptedException(String.format("Invalid header: Invalid data length %d", msg.dataLength));
+ throw new StreamCorruptedException(
+ String.format("Invalid header: Invalid data length %d", msg.dataLength));
+ }
+ if (msg.dataLength != 0 && (msg.command == A_OKAY
+ || msg.command == A_CLSE || msg.command == A_STLS)) {
+ throw new StreamCorruptedException(
+ String.format("Invalid header: Command 0x%x has a payload", msg.command));
}
if (msg.dataLength == 0) {
@@ -422,13 +386,7 @@ final class AdbProtocol {
// Read payload
msg.payload = new byte[msg.dataLength];
- dataRead = 0;
- do {
- int bytesRead = in.read(msg.payload, dataRead, msg.dataLength - dataRead);
- if (bytesRead < 0) {
- throw new IOException("Stream closed");
- } else dataRead += bytesRead;
- } while (dataRead < msg.dataLength);
+ readFully(in, msg.payload, msg.dataLength);
// Verify payload
if ((protocolVersion <= A_VERSION_MIN || (msg.command == A_CNXN && msg.arg0 <= A_VERSION_MIN))
@@ -440,6 +398,19 @@ final class AdbProtocol {
return msg;
}
+ // InputStream permits unusual implementations to return zero even
+ // when len is non-zero. Treat that as a broken transport instead of
+ // spinning forever on an attacker-controlled connection thread.
+ private static void readFully(InputStream in, byte[] data, int length) throws IOException {
+ int offset = 0;
+ while (offset < length) {
+ int count = in.read(data, offset, length - offset);
+ if (count < 0) throw new IOException("Stream closed");
+ if (count == 0) throw new IOException("ADB input made no progress");
+ offset += count;
+ }
+ }
+
private Message(@NonNull ByteBuffer header) {
command = header.getInt();
arg0 = header.getInt();
@@ -454,9 +425,6 @@ final class AdbProtocol {
public String toString() {
String tag;
switch (command) {
- case A_SYNC:
- tag = "SYNC";
- break;
case A_CNXN:
tag = "CNXN";
break;
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java
index 62754b0..1700831 100644
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java
@@ -4,6 +4,7 @@ package io.github.muntashirakon.adb;
import java.io.Closeable;
import java.io.IOException;
+import java.io.StreamCorruptedException;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -15,10 +16,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
// Copyright 2013 Cameron Gutman
public class AdbStream implements Closeable {
+ // Small transport seam around the connection. It keeps this stream state
+ // machine testable without a socket or Android runtime.
+ interface Transport {
+ int getMaxData();
+ void sendPacket(byte[] packet) throws IOException;
+ void flushPacket() throws IOException;
+ }
+
/**
* The AdbConnection object that the stream communicates over
*/
- private final AdbConnection mAdbConnection;
+ private final Transport mTransport;
/**
* The local ID of the stream
@@ -55,6 +64,9 @@ public class AdbStream implements Closeable {
*/
private volatile boolean mPendingClose;
+ /** One WRTE has been received and has not yet been fully consumed. */
+ private boolean mReadPacketPending;
+
/**
* Creates a new AdbStream object on the specified AdbConnection
* with the given local ID.
@@ -62,12 +74,11 @@ public class AdbStream implements Closeable {
* @param adbConnection AdbConnection that this stream is running on
* @param localId Local ID of the stream
*/
- AdbStream(AdbConnection adbConnection, int localId)
- throws IOException, InterruptedException, AdbPairingRequiredException {
- this.mAdbConnection = adbConnection;
+ AdbStream(Transport transport, int localId) {
+ this.mTransport = transport;
this.mLocalId = localId;
this.mReadQueue = new ConcurrentLinkedQueue<>();
- this.mReadBuffer = (ByteBuffer) ByteBuffer.allocate(adbConnection.getMaxData()).flip();
+ this.mReadBuffer = (ByteBuffer) ByteBuffer.allocate(transport.getMaxData()).flip();
this.mWriteReady = new AtomicBoolean(false);
this.mIsClosed = false;
}
@@ -85,8 +96,13 @@ public class AdbStream implements Closeable {
*
* @param payload Data inside the WRTE message
*/
- void addPayload(byte[] payload) {
+ void addPayload(byte[] payload) throws IOException {
synchronized (mReadQueue) {
+ if (mReadPacketPending) {
+ throw new StreamCorruptedException(
+ "ADB peer sent WRTE before the previous packet was acknowledged");
+ }
+ mReadPacketPending = true;
mReadQueue.add(payload);
mReadQueue.notifyAll();
}
@@ -100,7 +116,7 @@ public class AdbStream implements Closeable {
*/
void sendReady() throws IOException {
// Generate and send a OKAY packet
- mAdbConnection.sendPacket(AdbProtocol.generateReady(mLocalId, mRemoteId));
+ mTransport.sendPacket(AdbProtocol.generateReady(mLocalId, mRemoteId));
}
/**
@@ -109,14 +125,25 @@ public class AdbStream implements Closeable {
* @param remoteId New remote ID
*/
void updateRemoteId(int remoteId) {
+ if (remoteId == 0) throw new IllegalArgumentException("remote stream ID is zero");
+ if (mRemoteId != 0 && mRemoteId != remoteId) {
+ throw new IllegalStateException("remote stream ID changed");
+ }
this.mRemoteId = remoteId;
}
+ boolean hasRemoteId(int remoteId) {
+ return mRemoteId != 0 && mRemoteId == remoteId;
+ }
+
/**
* Called by the connection thread to indicate the stream is okay to send data.
*/
void readyForWrite() {
- mWriteReady.set(true);
+ synchronized (this) {
+ mWriteReady.set(true);
+ notifyAll();
+ }
}
boolean isOpen() {
@@ -128,7 +155,7 @@ public class AdbStream implements Closeable {
*/
void notifyClose(boolean closedByPeer) {
// We don't call close() because it sends another CLSE
- if (closedByPeer && !mReadQueue.isEmpty()) {
+ if (closedByPeer && hasUnreadData()) {
// The remote peer closed the stream, but we haven't finished reading the remaining data
mPendingClose = true;
} else {
@@ -151,42 +178,51 @@ public class AdbStream implements Closeable {
* @throws IOException If the stream fails while waiting
*/
public int read(byte[] bytes, int offset, int length) throws IOException {
+ if (bytes == null) throw new NullPointerException("bytes");
+ if (offset < 0 || length < 0 || offset > bytes.length || length > bytes.length - offset) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (length == 0) return 0;
if (mReadBuffer.hasRemaining()) {
- return readBuffer(bytes, offset, length);
+ return readBufferAndAcknowledge(bytes, offset, length);
}
- // Buffer has no data, grab from the queue
- synchronized (mReadQueue) {
- byte[] data;
- // Wait for the connection to close or data to be received
- while ((data = mReadQueue.poll()) == null && !mIsClosed) {
- try {
- mReadQueue.wait();
- } catch (InterruptedException e) {
- //noinspection UnnecessaryInitCause
- throw (IOException) new IOException().initCause(e);
+ while (true) {
+ // Buffer has no data, grab from the queue
+ synchronized (mReadQueue) {
+ byte[] data;
+ // Wait for the connection to close or data to be received
+ while ((data = mReadQueue.poll()) == null && !mIsClosed) {
+ try {
+ mReadQueue.wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw (IOException) new IOException("interrupted while reading ADB stream")
+ .initCause(e);
+ }
}
- }
- // Add data to the buffer
- if (data != null) {
- mReadBuffer.clear();
- mReadBuffer.put(data);
- mReadBuffer.flip();
- if (mReadBuffer.hasRemaining()) {
- return readBuffer(bytes, offset, length);
+
+ // Add data to the buffer
+ if (data != null) {
+ mReadBuffer.clear();
+ mReadBuffer.put(data);
+ mReadBuffer.flip();
+ if (mReadBuffer.hasRemaining()) {
+ return readBufferAndAcknowledge(bytes, offset, length);
+ }
+ // Empty WRTE packets are legal but carry no bytes. Ack
+ // and continue instead of returning a false EOF.
+ finishReadPacket();
}
- }
- if (mIsClosed) {
- throw new IOException("Stream closed.");
- }
+ if (mIsClosed) return -1;
- if (mPendingClose && mReadQueue.isEmpty()) {
- // The peer closed the stream, and we've finished reading the stream data, so this stream is finished
- mIsClosed = true;
+ if (mPendingClose && !hasUnreadData()) {
+ mPendingClose = false;
+ mIsClosed = true;
+ return -1;
+ }
}
}
-
- return -1;
}
private int readBuffer(byte[] bytes, int offset, int length) {
@@ -200,6 +236,32 @@ public class AdbStream implements Closeable {
return count;
}
+ private int readBufferAndAcknowledge(byte[] bytes, int offset, int length) throws IOException {
+ int count = readBuffer(bytes, offset, length);
+ if (!mReadBuffer.hasRemaining()) finishReadPacket();
+ return count;
+ }
+
+ private void finishReadPacket() throws IOException {
+ boolean closeAfterDrain;
+ synchronized (mReadQueue) {
+ mReadPacketPending = false;
+ closeAfterDrain = mPendingClose && mReadQueue.isEmpty();
+ if (closeAfterDrain) {
+ mPendingClose = false;
+ mIsClosed = true;
+ }
+ }
+ // A peer that already sent CLSE neither needs nor expects OKAY.
+ if (!closeAfterDrain && !mIsClosed) sendReady();
+ }
+
+ private boolean hasUnreadData() {
+ synchronized (mReadQueue) {
+ return mReadPacketPending || mReadBuffer.hasRemaining() || !mReadQueue.isEmpty();
+ }
+ }
+
/**
* Sends a WRTE packet with a given byte array payload. It does not flush the stream.
*
@@ -207,44 +269,37 @@ public class AdbStream implements Closeable {
* @throws IOException If the stream fails while sending data
*/
public void write(byte[] bytes, int offset, int length) throws IOException {
+ if (bytes == null) throw new NullPointerException("bytes");
+ if (offset < 0 || length < 0 || offset > bytes.length || length > bytes.length - offset) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (length == 0) return;
+ // Split and send data as WRTE packet
+ int maxData = mTransport.getMaxData();
+ while (length != 0) {
+ int count = Math.min(length, maxData);
+ sendWrite(bytes, offset, count);
+ offset += count;
+ length -= count;
+ }
+ }
+
+ private void sendWrite(byte[] bytes, int offset, int count) throws IOException {
synchronized (this) {
- // Make sure we're ready for a WRTE
while (!mIsClosed && !mWriteReady.compareAndSet(true, false)) {
try {
wait();
} catch (InterruptedException e) {
- //noinspection UnnecessaryInitCause
- throw (IOException) new IOException().initCause(e);
+ Thread.currentThread().interrupt();
+ throw (IOException) new IOException("interrupted while writing ADB stream")
+ .initCause(e);
}
}
-
- if (mIsClosed) {
- throw new IOException("Stream closed");
- }
- }
- // Split and send data as WRTE packet
- // TODO: A WRITE message may not be sent until a READY message is received.
- // Once a WRITE message is sent, an additional WRITE message may not be
- // sent until another READY message has been received. Recipients of
- // a WRITE message that is in violation of this requirement will CLOSE
- // the connection.
- int maxData;
- try {
- maxData = mAdbConnection.getMaxData();
- } catch (InterruptedException | AdbPairingRequiredException e) {
- //noinspection UnnecessaryInitCause
- throw (IOException) new IOException().initCause(e);
- }
- while (length != 0) {
- if (length <= maxData) {
- mAdbConnection.sendPacket(AdbProtocol.generateWrite(mLocalId, mRemoteId, bytes, offset, length));
- offset = offset + length;
- length = 0;
- } else { // if (length > maxData) {
- mAdbConnection.sendPacket(AdbProtocol.generateWrite(mLocalId, mRemoteId, bytes, offset, maxData));
- offset = offset + maxData;
- length = length - maxData;
- }
+ if (mIsClosed) throw new IOException("Stream closed");
+ // Keep consuming OKAY and sending WRTE atomic with close(). A
+ // concurrent close must never put WRTE on the wire after CLSE.
+ mTransport.sendPacket(
+ AdbProtocol.generateWrite(mLocalId, mRemoteId, bytes, offset, count));
}
}
@@ -252,7 +307,7 @@ public class AdbStream implements Closeable {
if (mIsClosed) {
throw new IOException("Stream closed");
}
- mAdbConnection.flushPacket();
+ mTransport.flushPacket();
}
/**
@@ -271,7 +326,7 @@ public class AdbStream implements Closeable {
notifyClose(false);
}
- mAdbConnection.sendPacket(AdbProtocol.generateClose(mLocalId, mRemoteId));
+ mTransport.sendPacket(AdbProtocol.generateClose(mLocalId, mRemoteId));
}
/**
@@ -291,12 +346,10 @@ public class AdbStream implements Closeable {
*/
public int available() throws IOException {
synchronized (this) {
- if (mIsClosed) {
- throw new IOException("Stream closed.");
- }
if (mReadBuffer.hasRemaining()) {
return mReadBuffer.remaining();
}
+ if (mIsClosed) return 0;
byte[] data = mReadQueue.peek();
return data == null ? 0 : data.length;
}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java
index 6aa12aa..91227b3 100644
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java
@@ -3,25 +3,18 @@
package io.github.muntashirakon.adb;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.bouncycastle.util.encoders.Base64;
+import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.security.GeneralSecurityException;
+import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
import java.security.interfaces.RSAPublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.RSAPublicKeySpec;
-import java.util.Objects;
-
-import javax.crypto.Cipher;
+import java.util.Locale;
final class AndroidPubkey {
/**
@@ -40,61 +33,6 @@ final class AndroidPubkey {
public static final int ANDROID_PUBKEY_MODULUS_SIZE_WORDS = ANDROID_PUBKEY_MODULUS_SIZE / 4;
/**
- * The RSA signature padding as an int array.
- */
- private static final int[] SIGNATURE_PADDING_AS_INT = new int[]{
- 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
- 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00,
- 0x04, 0x14
- };
-
- /**
- * The RSA signature padding as a byte array
- */
- private static final byte[] RSA_SHA_PKCS1_SIGNATURE_PADDING;
-
- static {
- RSA_SHA_PKCS1_SIGNATURE_PADDING = new byte[SIGNATURE_PADDING_AS_INT.length];
-
- for (int i = 0; i < RSA_SHA_PKCS1_SIGNATURE_PADDING.length; i++)
- RSA_SHA_PKCS1_SIGNATURE_PADDING[i] = (byte) SIGNATURE_PADDING_AS_INT[i];
- }
-
- /**
- * Signs the ADB SHA1 payload with the private key of this object.
- *
- * @param privateKey Private key to sign with
- * @param payload SHA1 payload to sign
- * @return Signed SHA1 payload
- * @throws GeneralSecurityException If signing fails
- */
- // Taken from adb_auth_sign
- @NonNull
- public static byte[] adbAuthSign(@NonNull PrivateKey privateKey, byte[] payload)
- throws GeneralSecurityException {
- Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
- c.init(Cipher.ENCRYPT_MODE, privateKey);
- c.update(RSA_SHA_PKCS1_SIGNATURE_PADDING);
- return c.doFinal(payload);
- }
-
- /**
* Converts a standard RSAPublicKey object to the special ADB format. Available since 4.2.2.
*
* @param publicKey RSAPublicKey object to convert
@@ -105,21 +43,24 @@ final class AndroidPubkey {
public static byte[] encodeWithName(@NonNull RSAPublicKey publicKey, @NonNull String name)
throws InvalidKeyException {
int pkeySize = 4 * (int) Math.ceil(ANDROID_PUBKEY_ENCODED_SIZE / 3.0);
- try (ByteArrayNoThrowOutputStream bos = new ByteArrayNoThrowOutputStream(pkeySize + name.length() + 2)) {
- bos.write(Base64.encode(encode(publicKey)));
- bos.write(getUserInfo(name));
- return bos.toByteArray();
- }
+ ByteArrayOutputStream out = new ByteArrayOutputStream(pkeySize + name.length() + 2);
+ byte[] encoded = Base64.encode(encode(publicKey));
+ out.write(encoded, 0, encoded.length);
+ byte[] userInfo = getUserInfo(name);
+ out.write(userInfo, 0, userInfo.length);
+ return out.toByteArray();
}
// Taken from get_user_info except that a custom name is used instead of host@user
@VisibleForTesting
@NonNull
static byte[] getUserInfo(@NonNull String name) {
- return StringCompat.getBytes(String.format(" %s\u0000", name), "UTF-8");
+ return String.format(Locale.ROOT, " %s\u0000", name)
+ .getBytes(StandardCharsets.UTF_8);
}
- // https://android.googlesource.com/platform/system/core/+/e797a5c75afc17024d0f0f488c130128fcd704e2/libcrypto_utils/android_pubkey.cpp
+ // AOSP platform/system/core commit e797a5c7,
+ // libcrypto_utils/android_pubkey.cpp:
// typedef struct RSAPublicKey {
// uint32_t modulus_size_words; // Modulus length. This must be ANDROID_PUBKEY_MODULUS_SIZE.
// uint32_t n0inv; // Precomputed montgomery parameter: -1 / n[0] mod 2^32
@@ -129,46 +70,6 @@ final class AndroidPubkey {
// } RSAPublicKey;
/**
- * Allocates a new {@link RSAPublicKey} object, decodes a public RSA key stored in Android's custom binary format,
- * and sets the key parameters. The resulting key can be used with the standard Java cryptography API to perform
- * public operations.
- *
- * @param androidPubkey Public RSA key in Android's custom binary format. The size of the key must be at least
- * {@link #ANDROID_PUBKEY_ENCODED_SIZE}
- * @return {@link RSAPublicKey} object
- */
- @NonNull
- public static RSAPublicKey decode(@NonNull byte[] androidPubkey)
- throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException {
- BigInteger n;
- BigInteger e;
-
- // Check size is large enough and the modulus size is correct.
- if (androidPubkey.length < ANDROID_PUBKEY_ENCODED_SIZE) {
- throw new InvalidKeyException("Invalid key length");
- }
- ByteBuffer keyStruct = ByteBuffer.wrap(androidPubkey).order(ByteOrder.LITTLE_ENDIAN);
- int modulusSize = keyStruct.getInt();
- if (modulusSize != ANDROID_PUBKEY_MODULUS_SIZE_WORDS) {
- throw new InvalidKeyException("Invalid modulus length.");
- }
-
- // Convert the modulus to big-endian byte order as expected by BN_bin2bn.
- byte[] modulus = new byte[ANDROID_PUBKEY_MODULUS_SIZE];
- keyStruct.position(8);
- keyStruct.get(modulus);
- n = new BigInteger(1, swapEndianness(modulus));
-
- // Read the exponent.
- keyStruct.position(520);
- e = BigInteger.valueOf(keyStruct.getInt());
-
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
- return (RSAPublicKey) keyFactory.generatePublic(publicKeySpec);
- }
-
- /**
* Encodes the given key in the Android RSA public key binary format.
*
* @return Public RSA key in Android's custom binary format. The size of the key should be at least
@@ -180,8 +81,13 @@ final class AndroidPubkey {
BigInteger n0inv;
BigInteger rr;
- if (publicKey.getModulus().toByteArray().length < ANDROID_PUBKEY_MODULUS_SIZE) {
- throw new InvalidKeyException("Invalid key length " + publicKey.getModulus().toByteArray().length);
+ if (publicKey.getModulus().bitLength() != ANDROID_PUBKEY_MODULUS_SIZE * 8) {
+ throw new InvalidKeyException("ADB requires an RSA-2048 key");
+ }
+ BigInteger exponent = publicKey.getPublicExponent();
+ if (!BigInteger.valueOf(3).equals(exponent)
+ && !BigInteger.valueOf(65537).equals(exponent)) {
+ throw new InvalidKeyException("ADB RSA exponent must be 3 or 65537");
}
ByteBuffer keyStruct = ByteBuffer.allocate(ANDROID_PUBKEY_ENCODED_SIZE).order(ByteOrder.LITTLE_ENDIAN);
@@ -196,12 +102,13 @@ final class AndroidPubkey {
keyStruct.putInt(n0inv.intValue()); // n0inv
// Store the modulus.
- keyStruct.put(Objects.requireNonNull(BigEndianToLittleEndianPadded(ANDROID_PUBKEY_MODULUS_SIZE, publicKey.getModulus())));
+ keyStruct.put(bigEndianToLittleEndianPadded(
+ ANDROID_PUBKEY_MODULUS_SIZE, publicKey.getModulus()));
// Compute and store rr = (2^(rsa_size)) ^ 2 mod N.
rr = BigInteger.ZERO.setBit(ANDROID_PUBKEY_MODULUS_SIZE * 8); // rr = 2^(rsa_size)
rr = rr.modPow(BigInteger.valueOf(2), publicKey.getModulus()); // rr = rr^2 mod N
- keyStruct.put(Objects.requireNonNull(BigEndianToLittleEndianPadded(ANDROID_PUBKEY_MODULUS_SIZE, rr)));
+ keyStruct.put(bigEndianToLittleEndianPadded(ANDROID_PUBKEY_MODULUS_SIZE, rr));
// Store the exponent.
keyStruct.putInt(publicKey.getPublicExponent().intValue()); // exponent
@@ -209,24 +116,24 @@ final class AndroidPubkey {
return keyStruct.array();
}
- @Nullable
- private static byte[] BigEndianToLittleEndianPadded(int len, @NonNull BigInteger in) {
+ private static byte[] bigEndianToLittleEndianPadded(int len, @NonNull BigInteger in)
+ throws InvalidKeyException {
byte[] out = new byte[len];
byte[] bytes = swapEndianness(in.toByteArray()); // Convert big endian -> little endian
- int num_bytes = bytes.length;
- if (len < num_bytes) {
- if (!fitsInBytes(bytes, num_bytes, len)) {
- return null;
+ int numBytes = bytes.length;
+ if (len < numBytes) {
+ if (!fitsInBytes(bytes, numBytes, len)) {
+ throw new InvalidKeyException("RSA value does not fit ADB key structure");
}
- num_bytes = len;
+ numBytes = len;
}
- System.arraycopy(bytes, 0, out, 0, num_bytes);
+ System.arraycopy(bytes, 0, out, 0, numBytes);
return out;
}
- static boolean fitsInBytes(@NonNull byte[] bytes, int num_bytes, int len) {
+ static boolean fitsInBytes(@NonNull byte[] bytes, int numBytes, int len) {
byte mask = 0;
- for (int i = len; i < num_bytes; i++) {
+ for (int i = len; i < numBytes; i++) {
mask |= bytes[i];
}
return mask == 0;
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/ByteArrayNoThrowOutputStream.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/ByteArrayNoThrowOutputStream.java
deleted file mode 100644
index 55cc1bf..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/ByteArrayNoThrowOutputStream.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
-
-package io.github.muntashirakon.adb;
-
-import java.io.ByteArrayOutputStream;
-
-class ByteArrayNoThrowOutputStream extends ByteArrayOutputStream {
- public ByteArrayNoThrowOutputStream() {
- super();
- }
-
- public ByteArrayNoThrowOutputStream(int size) {
- super(size);
- }
-
- @Override
- public void write(byte[] b) {
- write(b, 0, b.length);
- }
-
- @Override
- public void close() {
- }
-}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java
index 4574d8b..6323934 100644
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java
@@ -6,8 +6,6 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
-import javax.security.auth.DestroyFailedException;
-
final class KeyPair {
private final PrivateKey mPrivateKey;
private final Certificate mCertificate;
@@ -28,11 +26,4 @@ final class KeyPair {
public Certificate getCertificate() {
return mCertificate;
}
-
- public void destroy() throws DestroyFailedException {
- try {
- mPrivateKey.destroy();
- } catch (NoSuchMethodError ignore) {
- }
- }
}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/LocalServices.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/LocalServices.java
deleted file mode 100644
index 523bce3..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/LocalServices.java
+++ /dev/null
@@ -1,271 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
-
-package io.github.muntashirakon.adb;
-
-import android.text.TextUtils;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-
-/**
- * Local services extracted from the <a href="https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/client/commandline.cpp">ADB client</a>
- * for easy access.
- */
-public class LocalServices {
- static final int SERVICE_FIRST = 1;
-
- public static final int SHELL = 1;
- /**
- * Remount the device's filesystem in read-write mode, instead of read-only. This is usually necessary before
- * performing an {@link #SYNC} request. This request may not succeed on certain builds which do not allow that.
- * <p>
- * This essentially executes {@code /system/bin/remount} command. Additional arguments such as {@code -R} can be
- * passed too.
- */
- public static final int REMOUNT = 2;
- public static final int FILE = 3;
- public static final int TCP_CONNECT = 4;
- public static final int LOCAL_UNIX_SOCKET = 5;
- public static final int LOCAL_UNIX_SOCKET_RESERVED = 6;
- public static final int LOCAL_UNIX_SOCKET_ABSTRACT = 7;
- public static final int LOCAL_UNIX_SOCKET_FILE_SYSTEM = 8;
- /**
- * Receive snapshots of the framebuffer. It requires sufficient privileges (or the connection is closed immediately)
- * but works as follows:
- * <p>
- * After an {@link AdbStream} is opened, ADB daemon sends a 16-byte binary structure containing the following fields
- * (little-endian format):
- * <pre>
- * uint32_t depth; // framebuffer depth = 16
- * uint32_t size; // framebuffer size in bytes = 2 * width * height
- * uint32_t width; // framebuffer width in pixels
- * uint32_t height; // framebuffer height in pixels
- * </pre>
- * After that, each time a snapshot is wanted, one byte should be sent through the channel, which will trigger the
- * daemon to send {@code size} bytes of framebuffer data.
- */
- public static final int FRAMEBUFFER = 9;
- /**
- * Connects to the JDWP thread running in the VM of process PID (specified as an argument).
- */
- public static final int CONNECT_JDWP = 10;
- /**
- * Receive the list of JDWP PIDs periodically. The format of the returned data is the following (in order):
- * <ol>
- * <li> {@code hex4}: The length of all content as a 4-char hexadecimal string i.e. {@code %04zx}.
- * <li> {@code content}: A series of ASCII lines of the following format:
- * <pre>
- * &lt;pid&gt; "\n"
- * </pre>
- * </ol>
- * This service is used by DDMS to know which debuggable processes are running on the device/emulator.
- * <p>
- * Note that there is no single-shot service to retrieve the list only once.
- */
- public static final int TRACK_JDWP = 11;
- public static final int SYNC = 12;
- /**
- * Reverse socket connections from the device running ADB daemon to this client. This should not be used if both
- * the ADB daemon and the client are in the same device.
- * <p>
- * It takes an additional argument called {@code forward-command}. It can be one of the following:
- * <ul>
- * <li> {@code list-forward}: List all forwarded connections from the device
- * This returns something that looks like the following:
- * <ol>
- * <li> {@code hex4}: The length of the payload, as 4 hexadecimal chars i.e. {@code %04zx}.
- * <li> {@code payload}: A series of lines of the following format:
- * <pre>
- * host " " &lt;local&gt; " " &lt;remote&gt; "\n"
- * </pre>
- * Where &lt;local&gt; is the device-specific endpoint (e.g. {@code tcp:9000}), and &lt;remote&gt; is the
- * client-specific endpoint.
- * </ol>
- * <li> forward:<local>;<remote>
- * <li> forward:norebind:<local>;<remote>
- * <li> killforward-all
- * <li> killforward:<local>
- * </ul>
- */
- public static final int REVERSE = 13;
- /**
- * Backup some or all packages installed in the device. For this to work, {@code allowBackup=true} must be present
- * in the application section of the AndroidManifest.xml of the app.
- * <p>
- * It takes additional arguments which can be one of the following:
- * <ul>
- * <li>List of packages (as array)
- * <li>{@code -all}
- * <li>{@code -shared}
- * </ul>
- * Output is a stream which is in zlib format with 24 bytes at the front (if unencrypted).
- */
- public static final int BACKUP = 14;
- /**
- * Restore a backup. Input is a stream which is in zlib format with 24 bytes at the front (if unencrypted).
- */
- public static final int RESTORE = 15;
-
- static final int SERVICE_LAST = 15;
-
- @IntDef({
- SHELL,
- REMOUNT,
- FILE,
- TCP_CONNECT,
- LOCAL_UNIX_SOCKET,
- LOCAL_UNIX_SOCKET_RESERVED,
- LOCAL_UNIX_SOCKET_ABSTRACT,
- LOCAL_UNIX_SOCKET_FILE_SYSTEM,
- FRAMEBUFFER,
- CONNECT_JDWP,
- TRACK_JDWP,
- SYNC,
- REVERSE,
- BACKUP,
- RESTORE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Services {
- }
-
- @NonNull
- static String getServiceName(@Services int service) {
- switch (service) {
- case SHELL:
- return "shell:";
- case CONNECT_JDWP:
- return "jdwp:";
- case FILE:
- return "dev:";
- case FRAMEBUFFER:
- return "framebuffer:";
- case LOCAL_UNIX_SOCKET:
- return "local:";
- case LOCAL_UNIX_SOCKET_ABSTRACT:
- return "localabstract:";
- case LOCAL_UNIX_SOCKET_FILE_SYSTEM:
- return "localfilesystem:";
- case LOCAL_UNIX_SOCKET_RESERVED:
- return "localreserved:";
- case REMOUNT:
- return "remount:";
- case REVERSE:
- return "reverse:";
- case SYNC:
- return "sync:";
- case TCP_CONNECT:
- return "tcp:";
- case TRACK_JDWP:
- return "track-jdwp";
- case BACKUP:
- return "backup:";
- case RESTORE:
- return "restore:";
- default:
- throw new IllegalArgumentException("Invalid service: " + service);
- }
- }
-
- @NonNull
- static String getDestination(@Services int service, @NonNull String... args) {
- String serviceName = getServiceName(service);
- StringBuilder destination = new StringBuilder(serviceName);
- switch (service) {
- case SHELL:
- for (String arg : args) {
- if (arg.contains("\"")) {
- throw new IllegalArgumentException("Arguments for inline shell cannot contain double" +
- " quotations.");
- }
- if (arg.contains(" ")) {
- destination.append("\"").append(Objects.requireNonNull(arg)).append("\"");
- } else destination.append(Objects.requireNonNull(arg));
- }
- break;
- case FILE:
- if (args.length == 0) {
- throw new IllegalArgumentException("File name must be specified.");
- } else if (args.length != 1) {
- throw new IllegalArgumentException("Service expects exactly one argument, " + args.length
- + " supplied.");
- }
- destination.append(Objects.requireNonNull(args[0]));
- break;
- case TCP_CONNECT:
- if (args.length == 0) {
- throw new IllegalArgumentException("Port number must be specified.");
- } else if (args.length == 1) {
- destination.append(args[0]);
- } else if (args.length == 2) {
- destination.append(Objects.requireNonNull(args[0]))
- .append(':')
- .append(Objects.requireNonNull(args[1]));
- } else {
- throw new IllegalArgumentException("Invalid number of arguments supplied.");
- }
- break;
- case LOCAL_UNIX_SOCKET:
- case LOCAL_UNIX_SOCKET_ABSTRACT:
- case LOCAL_UNIX_SOCKET_FILE_SYSTEM:
- case LOCAL_UNIX_SOCKET_RESERVED:
- if (args.length == 0) {
- throw new IllegalArgumentException("Path must be specified.");
- } else if (args.length != 1) {
- throw new IllegalArgumentException("Service expects exactly one argument, " + args.length
- + " supplied.");
- }
- destination.append(Objects.requireNonNull(args[0]));
- break;
- case CONNECT_JDWP:
- if (args.length == 0) {
- throw new IllegalArgumentException("PID must be specified.");
- } else if (args.length != 1) {
- throw new IllegalArgumentException("Service expects exactly one argument, " + args.length
- + " supplied.");
- }
- destination.append(Objects.requireNonNull(args[0]));
- break;
- case REVERSE:
- if (args.length == 0) {
- throw new IllegalArgumentException("Forward command must be specified.");
- } else if (args.length != 1) {
- throw new IllegalArgumentException("Service expects exactly one argument, " + args.length
- + " supplied.");
- }
- if (args[0] == null) {
- throw new IllegalArgumentException("Forward command is empty");
- }
- if ("list-forward".equals(args[0]) || "killforward-all".equals(args[0])) {
- destination.append(args[0]);
- } else if (args[0].startsWith("forward:") || args[0].startsWith("killforward:")) {
- destination.append(args[0]);
- } else {
- throw new IllegalArgumentException("Invalid forward command.");
- }
- break;
- case BACKUP:
- if (args.length == 0) {
- throw new IllegalArgumentException("At least one package must be specified or use -shared/-all.");
- }
- case REMOUNT:
- // Additional arguments for the commands
- destination.append(TextUtils.join(" ", args));
- break;
- case RESTORE:
- case FRAMEBUFFER:
- case SYNC:
- case TRACK_JDWP:
- if (args.length != 0) {
- throw new IllegalArgumentException("Service expects no arguments.");
- }
- break;
- }
- return destination.toString();
- }
-}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PRNGFixes.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PRNGFixes.java
deleted file mode 100644
index fe6343f..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PRNGFixes.java
+++ /dev/null
@@ -1,319 +0,0 @@
-// SPDX-License-Identifier: MIT AND (GPL-3.0-or-later OR Apache-2.0)
-
-package io.github.muntashirakon.adb;
-
-import android.os.Build;
-import android.os.Process;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.SecureRandomSpi;
-import java.security.Security;
-
-/**
- * Fixes for the output of the default PRNG having low entropy.
- * <p>
- * The fixes need to be applied via {@link #apply()} before any use of Java
- * Cryptography Architecture primitives. A good place to invoke them is in the
- * application's {@code onCreate}.
- */
-// Copyright 2013 Google Inc.
-public final class PRNGFixes {
- private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
-
- /**
- * Hidden constructor to prevent instantiation.
- */
- private PRNGFixes() {
- }
-
- /**
- * Applies all fixes.
- *
- * @throws SecurityException if a fix is needed but could not be applied.
- */
- public static void apply() {
- applyOpenSSLFix();
- installLinuxPRNGSecureRandom();
- }
-
- /**
- * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
- * fix is not needed.
- *
- * @throws SecurityException if the fix is needed but could not be applied.
- */
- private static void applyOpenSSLFix() throws SecurityException {
- if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
- || (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2)) {
- // No need to apply the fix
- return;
- }
-
- try {
- // Mix in the device- and invocation-specific seed.
- Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
- .getMethod("RAND_seed", byte[].class)
- .invoke(null, generateSeed());
-
- // Mix output of Linux PRNG into OpenSSL's PRNG
- int bytesRead = (Integer) Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
- .getMethod("RAND_load_file", String.class, long.class)
- .invoke(null, "/dev/urandom", 1024);
- if (bytesRead != 1024) {
- throw new IOException("Unexpected number of bytes read from Linux PRNG: " + bytesRead);
- }
- } catch (Exception e) {
- throw new SecurityException("Failed to seed OpenSSL PRNG", e);
- }
- }
-
- /**
- * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
- * default. Does nothing if the implementation is already the default or if
- * there is not need to install the implementation.
- *
- * @throws SecurityException if the fix is needed but could not be applied.
- */
- private static void installLinuxPRNGSecureRandom()
- throws SecurityException {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
- // No need to apply the fix
- return;
- }
-
- // Install a Linux PRNG-based SecureRandom implementation as the
- // default, if not yet installed.
- Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
- if ((secureRandomProviders == null)
- || (secureRandomProviders.length < 1)
- || (!LinuxPRNGSecureRandomProvider.class.equals(secureRandomProviders[0].getClass()))) {
- Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
- }
-
- // Assert that new SecureRandom() and
- // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
- // by the Linux PRNG-based SecureRandom implementation.
- SecureRandom rng1 = new SecureRandom();
- if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) {
- throw new SecurityException("new SecureRandom() backed by wrong Provider: "
- + rng1.getProvider().getClass());
- }
-
- SecureRandom rng2;
- try {
- rng2 = SecureRandom.getInstance("SHA1PRNG");
- } catch (NoSuchAlgorithmException e) {
- throw new SecurityException("SHA1PRNG not available", e);
- }
- if (!LinuxPRNGSecureRandomProvider.class.equals(
- rng2.getProvider().getClass())) {
- throw new SecurityException("SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong provider: "
- + rng2.getProvider().getClass());
- }
- }
-
- /**
- * {@code Provider} of {@code SecureRandom} engines which pass through
- * all requests to the Linux PRNG.
- */
- private static class LinuxPRNGSecureRandomProvider extends Provider {
-
- public LinuxPRNGSecureRandomProvider() {
- super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses /dev/urandom");
- // Although /dev/urandom is not a SHA-1 PRNG, some apps
- // explicitly request a SHA1PRNG SecureRandom and we thus need to
- // prevent them from getting the default implementation whose output
- // may have low entropy.
- put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
- put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
- }
- }
-
- /**
- * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
- * ({@code /dev/urandom}).
- */
- public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
-
- /*
- * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
- * are passed through to the Linux PRNG (/dev/urandom). Instances of
- * this class seed themselves by mixing in the current time, PID, UID,
- * build fingerprint, and hardware serial number (where available) into
- * Linux PRNG.
- *
- * Concurrency: Read requests to the underlying Linux PRNG are
- * serialized (on sLock) to ensure that multiple threads do not get
- * duplicated PRNG output.
- */
-
- private static final File URANDOM_FILE = new File("/dev/urandom");
-
- private static final Object sLock = new Object();
-
- /**
- * Input stream for reading from Linux PRNG or {@code null} if not yet
- * opened.
- */
- @GuardedBy("sLock")
- private static DataInputStream sUrandomIn;
-
- /**
- * Output stream for writing to Linux PRNG or {@code null} if not yet
- * opened.
- */
- @GuardedBy("sLock")
- private static OutputStream sUrandomOut;
-
- /**
- * Whether this engine instance has been seeded. This is needed because
- * each instance needs to seed itself if the client does not explicitly
- * seed it.
- */
- private boolean mSeeded;
-
- @Override
- protected void engineSetSeed(byte[] bytes) {
- try {
- OutputStream out;
- synchronized (sLock) {
- out = getUrandomOutputStream();
- }
- out.write(bytes);
- out.flush();
- } catch (IOException e) {
- // On a small fraction of devices /dev/urandom is not writable.
- // Log and ignore.
- Log.w(PRNGFixes.class.getSimpleName(),
- "Failed to mix seed into " + URANDOM_FILE);
- } finally {
- mSeeded = true;
- }
- }
-
- @Override
- protected void engineNextBytes(byte[] bytes) {
- if (!mSeeded) {
- // Mix in the device- and invocation-specific seed.
- engineSetSeed(generateSeed());
- }
-
- try {
- DataInputStream in;
- synchronized (sLock) {
- in = getUrandomInputStream();
- }
- synchronized (in) {
- in.readFully(bytes);
- }
- } catch (IOException e) {
- throw new SecurityException(
- "Failed to read from " + URANDOM_FILE, e);
- }
- }
-
- @Override
- protected byte[] engineGenerateSeed(int size) {
- byte[] seed = new byte[size];
- engineNextBytes(seed);
- return seed;
- }
-
- private DataInputStream getUrandomInputStream() {
- synchronized (sLock) {
- if (sUrandomIn == null) {
- // NOTE: Consider inserting a BufferedInputStream between
- // DataInputStream and FileInputStream if you need higher
- // PRNG output performance and can live with future PRNG
- // output being pulled into this process prematurely.
- try {
- sUrandomIn = new DataInputStream(
- new FileInputStream(URANDOM_FILE));
- } catch (IOException e) {
- throw new SecurityException("Failed to open "
- + URANDOM_FILE + " for reading", e);
- }
- }
- return sUrandomIn;
- }
- }
-
- private OutputStream getUrandomOutputStream() throws IOException {
- synchronized (sLock) {
- if (sUrandomOut == null) {
- sUrandomOut = new FileOutputStream(URANDOM_FILE);
- }
- return sUrandomOut;
- }
- }
- }
-
- /**
- * Generates a device- and invocation-specific seed to be mixed into the
- * Linux PRNG.
- */
- private static byte[] generateSeed() {
- try {
- ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
- DataOutputStream seedBufferOut =
- new DataOutputStream(seedBuffer);
- seedBufferOut.writeLong(System.currentTimeMillis());
- seedBufferOut.writeLong(System.nanoTime());
- seedBufferOut.writeInt(Process.myPid());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BASE_1_1) {
- seedBufferOut.writeInt(Process.myUid());
- }
- seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
- seedBufferOut.close();
- return seedBuffer.toByteArray();
- } catch (IOException e) {
- throw new SecurityException("Failed to generate seed", e);
- }
- }
-
- /**
- * Gets the hardware serial number of this device.
- *
- * @return serial number or {@code null} if not available.
- */
- private static String getDeviceSerialNumber() {
- // We're using the Reflection API because Build.SERIAL is only available
- // since API Level 9 (Gingerbread, Android 2.3).
- try {
- return (String) Build.class.getField("SERIAL").get(null);
- } catch (Exception ignored) {
- return null;
- }
- }
-
- private static byte[] getBuildFingerprintAndDeviceSerial() {
- StringBuilder result = new StringBuilder();
- String fingerprint = Build.FINGERPRINT;
- if (fingerprint != null) {
- result.append(fingerprint);
- }
- String serial = getDeviceSerialNumber();
- if (serial != null) {
- result.append(serial);
- }
- try {
- return result.toString().getBytes("UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("UTF-8 encoding not supported");
- }
- }
-} \ No newline at end of file
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java
index a329c29..412355c 100644
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java
@@ -2,11 +2,8 @@
package io.github.muntashirakon.adb;
-import android.os.Build;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import org.bouncycastle.crypto.InvalidCipherTextException;
@@ -21,6 +18,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import javax.security.auth.Destroyable;
@@ -28,16 +26,18 @@ import javax.security.auth.Destroyable;
import io.github.muntashirakon.crypto.spake2.Spake2Context;
import io.github.muntashirakon.crypto.spake2.Spake2Role;
-@RequiresApi(Build.VERSION_CODES.GINGERBREAD)
class PairingAuthCtx implements Destroyable {
// The following values are taken from the following source and are subjected to change
// https://github.com/aosp-mirror/platform_system_core/blob/android-11.0.0_r1/adb/pairing_auth/pairing_auth.cpp
- private static final byte[] CLIENT_NAME = StringCompat.getBytes("adb pair client\u0000", "UTF-8");
- private static final byte[] SERVER_NAME = StringCompat.getBytes("adb pair server\u0000", "UTF-8");
+ private static final byte[] CLIENT_NAME =
+ "adb pair client\u0000".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] SERVER_NAME =
+ "adb pair server\u0000".getBytes(StandardCharsets.UTF_8);
// The following values are taken from the following source and are subjected to change
// https://github.com/aosp-mirror/platform_system_core/blob/android-11.0.0_r1/adb/pairing_auth/aes_128_gcm.cpp
- private static final byte[] INFO = StringCompat.getBytes("adb pairing_auth aes-128-gcm key", "UTF-8");
+ private static final byte[] INFO =
+ "adb pairing_auth aes-128-gcm key".getBytes(StandardCharsets.UTF_8);
private static final int HKDF_KEY_LENGTH = 128 / 8;
public static final int GCM_IV_LENGTH = 12; // in bytes
@@ -83,10 +83,14 @@ class PairingAuthCtx implements Destroyable {
if (mIsDestroyed) return false;
byte[] keyMaterial = mSpake2Ctx.processMessage(theirMsg);
if (keyMaterial == null) return false;
- HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest());
- hkdf.init(new HKDFParameters(keyMaterial, null, INFO));
- hkdf.generateBytes(mSecretKey, 0, mSecretKey.length);
- return true;
+ try {
+ HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest());
+ hkdf.init(new HKDFParameters(keyMaterial, null, INFO));
+ hkdf.generateBytes(mSecretKey, 0, mSecretKey.length);
+ return true;
+ } finally {
+ Arrays.fill(keyMaterial, (byte) 0);
+ }
}
@Nullable
@@ -108,7 +112,9 @@ class PairingAuthCtx implements Destroyable {
@Override
public void destroy() {
+ if (mIsDestroyed) return;
mIsDestroyed = true;
+ Arrays.fill(mMsg, (byte) 0);
Arrays.fill(mSecretKey, (byte) 0);
mSpake2Ctx.destroy();
}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java
index 732a08e..b009e66 100644
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java
@@ -2,19 +2,18 @@
package io.github.muntashirakon.adb;
-import android.annotation.SuppressLint;
-import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
+
+import org.conscrypt.Conscrypt;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
-import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -28,18 +27,17 @@ import java.util.Arrays;
import java.util.Objects;
import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
-// https://github.com/aosp-mirror/platform_system_core/blob/android-11.0.0_r1/adb/pairing_connection/pairing_connection.cpp
+// AOSP android-11.0.0_r1 adb/pairing_connection/pairing_connection.cpp.
// Also based on Shizuku's implementation
-@RequiresApi(Build.VERSION_CODES.GINGERBREAD)
public final class PairingConnectionCtx implements Closeable {
- public static final String TAG = PairingConnectionCtx.class.getSimpleName();
+ public static final String TAG = "scrcpy-android";
public static final String EXPORTED_KEY_LABEL = "adb-label\u0000";
public static final int EXPORT_KEY_SIZE = 64;
+ private static final int CONNECT_TIMEOUT_MS = 10_000;
+ private static final int IO_TIMEOUT_MS = 15_000;
private enum State {
Ready,
@@ -48,29 +46,29 @@ public final class PairingConnectionCtx implements Closeable {
Stopped
}
- enum Role {
- Client,
- Server,
- }
-
private final String mHost;
private final int mPort;
private final byte[] mPswd;
private final PeerInfo mPeerInfo;
private final SSLContext mSslContext;
- private final Role mRole = Role.Client;
-
private DataInputStream mInputStream;
private DataOutputStream mOutputStream;
private PairingAuthCtx mPairingAuthCtx;
+ private Socket mSocket;
private State mState = State.Ready;
public PairingConnectionCtx(@NonNull String host, int port, @NonNull byte[] pswd, @NonNull KeyPair keyPair,
@NonNull String deviceName)
throws NoSuchAlgorithmException, KeyManagementException, InvalidKeyException {
this.mHost = Objects.requireNonNull(host);
+ if (host.isEmpty()) throw new IllegalArgumentException("host is empty");
+ if (port < 1 || port > 65535) throw new IllegalArgumentException("port is invalid");
+ Objects.requireNonNull(pswd);
+ if (pswd.length == 0 || pswd.length > 1024) {
+ throw new IllegalArgumentException("password length is invalid");
+ }
this.mPort = port;
- this.mPswd = Objects.requireNonNull(pswd);
+ this.mPswd = pswd.clone();
this.mPeerInfo = new PeerInfo(PeerInfo.ADB_RSA_PUB_KEY, AndroidPubkey.encodeWithName((RSAPublicKey)
keyPair.getPublicKey(), Objects.requireNonNull(deviceName)));
this.mSslContext = SslUtils.getSslContext(keyPair);
@@ -121,17 +119,19 @@ public final class PairingConnectionCtx implements Closeable {
}
private void setupTlsConnection() throws IOException {
- Socket socket;
- if (mRole == Role.Server) {
- SSLServerSocket sslServerSocket = (SSLServerSocket) mSslContext.getServerSocketFactory().createServerSocket(mPort);
- socket = sslServerSocket.accept();
- // TODO: Write automated test scripts after removing Conscrypt dependency.
- } else { // role == Role.Client
- socket = new Socket(mHost, mPort);
+ Socket socket = new Socket();
+ try {
+ socket.connect(new InetSocketAddress(mHost, mPort), CONNECT_TIMEOUT_MS);
+ socket.setSoTimeout(IO_TIMEOUT_MS);
+ socket.setTcpNoDelay(true);
+ } catch (IOException e) {
+ try { socket.close(); } catch (IOException ignored) {}
+ throw e;
}
- socket.setTcpNoDelay(true);
+ mSocket = socket;
- // We use custom SSLContext to allow any SSL certificates
+ // The PAKE below authenticates the pairing code and binds it to this
+ // TLS channel through exported key material.
SSLSocket sslSocket = (SSLSocket) mSslContext.getSocketFactory().createSocket(socket, mHost, mPort, true);
sslSocket.startHandshake();
Log.d(TAG, "Handshake succeeded.");
@@ -141,41 +141,22 @@ public final class PairingConnectionCtx implements Closeable {
// To ensure the connection is not stolen while we do the PAKE, append the exported key material from the
// tls connection to the password.
- byte[] keyMaterial = exportKeyingMaterial(sslSocket, EXPORT_KEY_SIZE);
+ byte[] keyMaterial = Conscrypt.exportKeyingMaterial(
+ sslSocket, EXPORTED_KEY_LABEL, null, EXPORT_KEY_SIZE);
byte[] passwordBytes = new byte[mPswd.length + keyMaterial.length];
- System.arraycopy(mPswd, 0, passwordBytes, 0, mPswd.length);
- System.arraycopy(keyMaterial, 0, passwordBytes, mPswd.length, keyMaterial.length);
-
- PairingAuthCtx pairingAuthCtx = PairingAuthCtx.createAlice(passwordBytes);
- if (pairingAuthCtx == null) {
- throw new IOException("Unable to create PairingAuthCtx.");
- }
- this.mPairingAuthCtx = pairingAuthCtx;
- }
-
- @SuppressLint("PrivateApi") // Conscrypt is a stable private API
- private byte[] exportKeyingMaterial(SSLSocket sslSocket, int length) throws SSLException {
- // Conscrypt#exportKeyingMaterial(SSLSocket socket, String label, byte[] context, int length): byte[]
- // throws SSLException
+ PairingAuthCtx pairingAuthCtx;
try {
- Class<?> conscryptClass;
- if (SslUtils.isCustomConscrypt()) {
- conscryptClass = Class.forName("org.conscrypt.Conscrypt");
- } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- // Although support for conscrypt has been added in Android 5.0 (Lollipop),
- // TLS1.3 isn't supported until Android 9 (Pie).
- throw new SSLException("TLSv1.3 isn't supported on your platform. Use custom Conscrypt library instead.");
- } else {
- conscryptClass = Class.forName("com.android.org.conscrypt.Conscrypt");
+ System.arraycopy(mPswd, 0, passwordBytes, 0, mPswd.length);
+ System.arraycopy(keyMaterial, 0, passwordBytes, mPswd.length, keyMaterial.length);
+ pairingAuthCtx = PairingAuthCtx.createAlice(passwordBytes);
+ if (pairingAuthCtx == null) {
+ throw new IOException("Unable to create PairingAuthCtx.");
}
- Method exportKeyingMaterial = conscryptClass.getMethod("exportKeyingMaterial", SSLSocket.class,
- String.class, byte[].class, int.class);
- return (byte[]) exportKeyingMaterial.invoke(null, sslSocket, EXPORTED_KEY_LABEL, null, length);
- } catch (SSLException e) {
- throw e;
- } catch (Throwable th) {
- throw new SSLException(th);
+ } finally {
+ Arrays.fill(keyMaterial, (byte) 0);
+ Arrays.fill(passwordBytes, (byte) 0);
}
+ this.mPairingAuthCtx = pairingAuthCtx;
}
private void writeHeader(@NonNull PairingPacketHeader header, @NonNull byte[] payload) throws IOException {
@@ -269,31 +250,50 @@ public final class PairingConnectionCtx implements Closeable {
}
PeerInfo theirPeerInfo = PeerInfo.readFrom(ByteBuffer.wrap(decryptedMsg));
- Log.d(TAG, theirPeerInfo.toString());
+ Log.d(TAG, "Received peer info type=" + theirPeerInfo.type);
+ if (theirPeerInfo.type != PeerInfo.ADB_DEVICE_GUID) {
+ Log.e(TAG, "Pairing peer did not send a device GUID");
+ return false;
+ }
+ if (theirPeerInfo.data[0] == 0) {
+ Log.e(TAG, "Pairing peer sent an empty device GUID");
+ return false;
+ }
return true;
}
@Override
public void close() {
Arrays.fill(mPswd, (byte) 0);
- try {
- mInputStream.close();
- } catch (IOException ignore) {
+ if (mInputStream != null) {
+ try {
+ mInputStream.close();
+ } catch (IOException ignore) {
+ }
}
- try {
- mOutputStream.close();
- } catch (IOException ignore) {
+ if (mOutputStream != null) {
+ try {
+ mOutputStream.close();
+ } catch (IOException ignore) {
+ }
}
- if (mState != State.Ready) {
+ if (mSocket != null) {
+ try {
+ mSocket.close();
+ } catch (IOException ignore) {
+ }
+ }
+ if (mPairingAuthCtx != null) {
mPairingAuthCtx.destroy();
}
+ mState = State.Stopped;
}
private static class PeerInfo {
public static final int MAX_PEER_INFO_SIZE = 1 << 13;
public static final byte ADB_RSA_PUB_KEY = 0;
- public static final byte ADB_DEVICE_GUID = 0;
+ public static final byte ADB_DEVICE_GUID = 1;
@NonNull
public static PeerInfo readFrom(@NonNull ByteBuffer buffer) {
@@ -314,15 +314,6 @@ public final class PairingConnectionCtx implements Closeable {
public void writeTo(@NonNull ByteBuffer buffer) {
buffer.put(type).put(data);
}
-
- @NonNull
- @Override
- public String toString() {
- return "PeerInfo{" +
- "type=" + type +
- ", data=" + Arrays.toString(data) +
- '}';
- }
}
private static class PairingPacketHeader {
@@ -370,15 +361,5 @@ public final class PairingConnectionCtx implements Closeable {
public void writeTo(@NonNull ByteBuffer buffer) {
buffer.put(version).put(type).putInt(payloadSize);
}
-
- @NonNull
- @Override
- public String toString() {
- return "PairingPacketHeader{" +
- "version=" + version +
- ", type=" + type +
- ", payloadSize=" + payloadSize +
- '}';
- }
}
}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java
index aea4e6f..0e5db6b 100644
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java
@@ -2,19 +2,21 @@
package io.github.muntashirakon.adb;
-import android.annotation.SuppressLint;
-import android.os.Build;
-
import androidx.annotation.NonNull;
+import org.conscrypt.Conscrypt;
+
+import java.math.BigInteger;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
-import java.security.Provider;
import java.security.SecureRandom;
+import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Objects;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
@@ -22,45 +24,25 @@ import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;
final class SslUtils {
- private static boolean customConscrypt = false;
- private static SSLContext sslContext;
-
- public static boolean isCustomConscrypt() {
- return customConscrypt;
- }
+ private SslUtils() {}
- @SuppressLint("TrulyRandom") // The users are already instructed to fix this issue
@NonNull
- public static SSLContext getSslContext(KeyPair keyPair) throws NoSuchAlgorithmException, KeyManagementException {
- if (sslContext != null) {
- return sslContext;
- }
- try {
- Class<?> providerClass = Class.forName("org.conscrypt.OpenSSLProvider");
- Provider openSslProvder = (Provider) providerClass.getDeclaredConstructor().newInstance();
- sslContext = SSLContext.getInstance("TLSv1.3", openSslProvder);
- customConscrypt = true;
- } catch (NoSuchAlgorithmException e) {
- throw e;
- } catch (Throwable e) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- // Custom error message to inform user that they should use custom Conscrypt library.
- throw new NoSuchAlgorithmException("TLSv1.3 isn't supported on your platform. Use custom Conscrypt library instead.");
- }
- sslContext = SSLContext.getInstance("TLSv1.3");
- customConscrypt = false;
+ static SSLContext getSslContext(KeyPair keyPair)
+ throws NoSuchAlgorithmException, KeyManagementException {
+ Objects.requireNonNull(keyPair);
+ if (!Conscrypt.isAvailable()) {
+ throw new NoSuchAlgorithmException("bundled Conscrypt is unavailable");
}
- System.out.println("Using " + (customConscrypt ? "custom" : "default") + " TLSv1.3 provider...");
- sslContext.init(new KeyManager[]{getKeyManager(keyPair)},
- new X509TrustManager[]{getAllAcceptingTrustManager()},
- new SecureRandom());
- return sslContext;
+ SSLContext context = SSLContext.getInstance("TLSv1.3", Conscrypt.newProvider());
+ context.init(new KeyManager[]{getKeyManager(keyPair)},
+ new X509TrustManager[]{getAdbTrustManager()}, new SecureRandom());
+ return context;
}
@NonNull
private static KeyManager getKeyManager(KeyPair keyPair) {
return new X509ExtendedKeyManager() {
- private final String mAlias = "key";
+ private static final String ALIAS = "key";
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
@@ -70,7 +52,7 @@ final class SslUtils {
@Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
for (String keyType : keyTypes) {
- if (keyType.equals("RSA")) return mAlias;
+ if ("RSA".equals(keyType)) return ALIAS;
}
return null;
}
@@ -87,32 +69,45 @@ final class SslUtils {
@Override
public X509Certificate[] getCertificateChain(String alias) {
- if (this.mAlias.equals(alias)) {
- return new X509Certificate[]{(X509Certificate) keyPair.getCertificate()};
- }
- return null;
+ if (!ALIAS.equals(alias)) return null;
+ return new X509Certificate[]{(X509Certificate) keyPair.getCertificate()};
}
@Override
public PrivateKey getPrivateKey(String alias) {
- if (this.mAlias.equals(alias)) {
- return keyPair.getPrivateKey();
- }
- return null;
+ return ALIAS.equals(alias) ? keyPair.getPrivateKey() : null;
}
};
}
- @SuppressLint("TrustAllX509TrustManager") // Accept all certificates
+ // ADB does not give the client a stable target certificate to verify.
+ // The pairing server generates a new key for each pairing operation and
+ // adbd generates a separate process-scoped key. Pairing authenticates the
+ // six-digit-code exchange; later TLS connections authenticate this client
+ // to adbd, but not adbd to this client. Reject malformed certificates and
+ // keep TLS mandatory without pretending that an ephemeral key is an
+ // identity pin.
@NonNull
- private static X509TrustManager getAllAcceptingTrustManager() {
+ private static X509TrustManager getAdbTrustManager() {
return new X509TrustManager() {
@Override
- public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ throw new CertificateException("ADB TLS context is client-only");
}
@Override
- public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ if (chain == null || chain.length == 0) {
+ throw new CertificateException("ADB peer presented no certificate");
+ }
+ if (!(chain[0].getPublicKey() instanceof RSAPublicKey key)
+ || key.getModulus().bitLength() != 2048
+ || (!BigInteger.valueOf(3).equals(key.getPublicExponent())
+ && !BigInteger.valueOf(65537).equals(key.getPublicExponent()))) {
+ throw new CertificateException("ADB peer certificate is not RSA-2048");
+ }
}
@Override
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/StringCompat.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/StringCompat.java
deleted file mode 100644
index 1e05eee..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/StringCompat.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
-
-package io.github.muntashirakon.adb;
-
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.IllegalCharsetNameException;
-
-final class StringCompat {
- @NonNull
- public static byte[] getBytes(@NonNull String text, @NonNull String charsetName) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
- return text.getBytes(Charset.forName(charsetName));
- }
- try {
- return text.getBytes(charsetName);
- } catch (UnsupportedEncodingException e) {
- throw (IllegalCharsetNameException) new IllegalCharsetNameException("Illegal charset " + charsetName)
- .initCause(e);
- }
- }
-}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AdbMdns.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AdbMdns.java
deleted file mode 100644
index 6b424cb..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AdbMdns.java
+++ /dev/null
@@ -1,193 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
-
-package io.github.muntashirakon.adb.android;
-
-import android.content.Context;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.StringDef;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.NetworkInterface;
-import java.net.ServerSocket;
-import java.net.SocketException;
-import java.util.Collections;
-import java.util.Objects;
-
-/**
- * Automatic discovery of ADB daemons.
- */
-// Copyright 2020 南宫雪珊
-// Copyright 2022 Muntashir Al-Islam
-// Based on https://android.googlesource.com/platform/packages/modules/adb/+/eddd2d3a386a83f5d1e14f87a318adef4c2f1a9d/adb_mdns.cpp
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-public class AdbMdns {
- public static final String SERVICE_TYPE_ADB = "adb";
- public static final String SERVICE_TYPE_TLS_PAIRING = "adb-tls-pairing";
- public static final String SERVICE_TYPE_TLS_CONNECT = "adb-tls-connect";
-
- @StringDef({
- SERVICE_TYPE_ADB,
- SERVICE_TYPE_TLS_PAIRING,
- SERVICE_TYPE_TLS_CONNECT,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ServiceType {
- }
-
- public interface OnAdbDaemonDiscoveredListener {
- void onPortChanged(@Nullable InetAddress hostAddress, int port);
- }
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final String mServiceType;
- @NonNull
- private final OnAdbDaemonDiscoveredListener mAdbDaemonDiscoveredListener;
- private final NsdManager.DiscoveryListener mDiscoveryListener;
- private final NsdManager mNsdManager;
-
- private boolean mRegistered;
- private boolean mRunning;
- @Nullable
- private String mServiceName;
-
- public AdbMdns(@NonNull Context context, @ServiceType @NonNull String serviceType,
- @NonNull OnAdbDaemonDiscoveredListener portChangeListener) {
- mContext = Objects.requireNonNull(context);
- mServiceType = String.format("_%s._tcp", Objects.requireNonNull(serviceType));
- mAdbDaemonDiscoveredListener = Objects.requireNonNull(portChangeListener);
- mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
- mDiscoveryListener = new DiscoveryListener(this);
- }
-
- public void start() {
- if (mRunning) return;
- mRunning = true;
- if (!mRegistered) {
- mNsdManager.discoverServices(mServiceType, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
- }
- }
-
- public void stop() {
- if (!mRunning) return;
- mRunning = false;
- if (mRegistered) {
- mNsdManager.stopServiceDiscovery(mDiscoveryListener);
- }
- }
-
- public boolean isRunning() {
- return mRunning;
- }
-
- private void onDiscoveryStart() {
- mRegistered = true;
- }
-
- private void onDiscoverStop() {
- mRegistered = false;
- }
-
- private void onServiceFound(NsdServiceInfo serviceInfo) {
- mNsdManager.resolveService(serviceInfo, new ResolveListener(this));
- }
-
- private void onServiceLost(NsdServiceInfo serviceInfo) {
- if (mServiceName != null && mServiceName.equals(serviceInfo.getServiceName())) {
- mAdbDaemonDiscoveredListener.onPortChanged(serviceInfo.getHost(), -1);
- }
- }
-
- private void onServiceResolved(NsdServiceInfo serviceInfo) {
- if (!mRunning) return;
- try {
- for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
- for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) {
- String inetHost = inetAddress.getHostAddress();
- if (Objects.equals(inetHost, serviceInfo.getHost().getHostAddress())
- && isPortAvailable(serviceInfo.getPort())) {
- mServiceName = serviceInfo.getServiceName();
- mAdbDaemonDiscoveredListener.onPortChanged(serviceInfo.getHost(), serviceInfo.getPort());
- }
- }
- }
- } catch (SocketException e) {
- e.printStackTrace();
- }
- }
-
- private boolean isPortAvailable(int port) {
- try (ServerSocket socket = new ServerSocket()) {
- socket.bind(new InetSocketAddress(AndroidUtils.getHostIpAddress(mContext), port), 1);
- return false;
- } catch (IOException e) {
- return true;
- }
- }
-
- private static class DiscoveryListener implements NsdManager.DiscoveryListener {
- @NonNull
- private final AdbMdns mAdbMdns;
-
- private DiscoveryListener(@NonNull AdbMdns adbMdns) {
- mAdbMdns = adbMdns;
- }
-
- @Override
- public void onDiscoveryStarted(String serviceType) {
- mAdbMdns.onDiscoveryStart();
- }
-
- @Override
- public void onStartDiscoveryFailed(String serviceType, int errorCode) {
- }
-
- @Override
- public void onDiscoveryStopped(String serviceType) {
- mAdbMdns.onDiscoverStop();
- }
-
- @Override
- public void onStopDiscoveryFailed(String serviceType, int errorCode) {
- }
-
- @Override
- public void onServiceFound(NsdServiceInfo serviceInfo) {
- mAdbMdns.onServiceFound(serviceInfo);
- }
-
- @Override
- public void onServiceLost(NsdServiceInfo serviceInfo) {
- mAdbMdns.onServiceLost(serviceInfo);
- }
- }
-
- private static class ResolveListener implements NsdManager.ResolveListener {
- @NonNull
- private final AdbMdns mAdbMdns;
-
- private ResolveListener(@NonNull AdbMdns adbMdns) {
- mAdbMdns = adbMdns;
- }
-
- @Override
- public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
- }
-
- @Override
- public void onServiceResolved(NsdServiceInfo serviceInfo) {
- mAdbMdns.onServiceResolved(serviceInfo);
- }
- }
-}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AndroidUtils.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AndroidUtils.java
deleted file mode 100644
index bb679d2..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AndroidUtils.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
-
-package io.github.muntashirakon.adb.android;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.os.Build;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-public class AndroidUtils {
- // https://github.com/firebase/firebase-android-sdk/blob/7d86138304a6573cbe2c61b66b247e930fa05767/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CommonUtils.java#L402
- private static final String GOLDFISH = "goldfish";
- private static final String RANCHU = "ranchu";
- private static final String SDK = "sdk";
-
- public static boolean isEmulator(@NonNull Context context) {
- if (Build.PRODUCT.contains(SDK)) {
- return true;
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO
- && (Build.HARDWARE.contains(GOLDFISH) || Build.HARDWARE.contains(RANCHU))) {
- return true;
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
- @SuppressLint("HardwareIds")
- String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
- return androidId == null;
- }
- return false;
- }
-
-
- @NonNull
- public static String getHostIpAddress(@NonNull Context context) {
- if (AndroidUtils.isEmulator(context)) {
- return "10.0.2.2";
- }
- String ipAddress;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- ipAddress = InetAddress.getLoopbackAddress().getHostAddress();
- } else {
- try {
- ipAddress = InetAddress.getLocalHost().getHostAddress();
- } catch (UnknownHostException e) {
- ipAddress = null;
- }
- }
- if (ipAddress == null || ipAddress.equals("::1")) {
- return "127.0.0.1";
- }
- return ipAddress;
- }
-}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/package.html b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/package.html
deleted file mode 100644
index 704dd26..0000000
--- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/package.html
+++ /dev/null
@@ -1 +0,0 @@
-<p>All Android dependencies are kept under this package for easy reference.</p> \ No newline at end of file