aboutsummaryrefslogtreecommitdiff
path: root/vendor/libadb-android/libadb/src/main/java/io/github
diff options
context:
space:
mode:
authorLena <lena@omega>2026-01-01 00:00:00 +0000
committerLena <lena@omega>2026-06-24 22:14:50 +0300
commiteb0c8951196c637e44daf3c0617b131b997d5d2c (patch)
tree2f83b6a41cee745467e53fc401942b82cea286ea /vendor/libadb-android/libadb/src/main/java/io/github
downloadscrcpy-android-0.1.tar.gz
scrcpy-android: mirror an Android device over wireless ADB0.1
Native Java app for Android 12+ that mirrors another Android device over wireless ADB, forwarding video, audio, touch input, and clipboard. Bundles a pinned scrcpy-server.jar and the vendored libadb-android stack. Supports h264/h265/av1 video and raw/opus audio with in-app codec selection. No NDK, no Kotlin. Includes JVM unit tests and a Docker-based emulator e2e rig.
Diffstat (limited to 'vendor/libadb-android/libadb/src/main/java/io/github')
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java508
-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.java749
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java43
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbOutputStream.java39
-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.java497
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java300
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java244
-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.java38
-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.java131
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java384
-rw-r--r--vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java124
-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
20 files changed, 3970 insertions, 0 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
new file mode 100644
index 0000000..967c47a
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java
@@ -0,0 +1,508 @@
+// 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);
+ } 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(service, args);
+ } 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
new file mode 100644
index 0000000..bf50eeb
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbAuthenticationFailedException.java
@@ -0,0 +1,14 @@
+// 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
new file mode 100644
index 0000000..a21cb66
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java
@@ -0,0 +1,749 @@
+// SPDX-License-Identifier: BSD-3-Clause AND (GPL-3.0-or-later OR Apache-2.0)
+
+package io.github.muntashirakon.adb;
+
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.Socket;
+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();
+
+ /**
+ * The underlying socket that this class uses to communicate with the target device.
+ */
+ @NonNull
+ private final Socket mSocket;
+
+ @NonNull
+ private final String mHost;
+
+ 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.
+ */
+ private int mLastLocalId;
+
+ /**
+ * The input stream that this class uses to read from the socket.
+ */
+ @GuardedBy("lock")
+ @NonNull
+ private final InputStream mPlainInputStream;
+
+ /**
+ * The output stream that this class uses to read from the socket.
+ */
+ @GuardedBy("lock")
+ @NonNull
+ private final OutputStream mPlainOutputStream;
+
+ /**
+ * The input stream that this class uses to read from the TLS socket.
+ */
+ @GuardedBy("lock")
+ @Nullable
+ private volatile InputStream mTlsInputStream;
+
+ /**
+ * The output stream that this class uses to read from the TLS socket.
+ */
+ @GuardedBy("lock")
+ @Nullable
+ private volatile OutputStream mTlsOutputStream;
+
+ /**
+ * The backend thread that handles responding to ADB packets.
+ */
+ @NonNull
+ private final Thread mConnectionThread;
+
+ /**
+ * Specifies whether a CNXN has been attempted.
+ */
+ 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;
+
+ /**
+ * Specifies whether a CNXN packet has been received from the peer.
+ */
+ private volatile boolean mConnectionEstablished;
+
+ /**
+ * Exceptions that occur in {@link #createConnectionThread()}.
+ */
+ @Nullable
+ private volatile Exception mConnectionException;
+
+ /**
+ * Specifies the maximum amount data that can be sent to the remote peer.
+ * This is only valid after connect() returns successfully.
+ */
+ private volatile int mMaxData;
+
+ private volatile int mProtocolVersion;
+
+ @NonNull
+ private final KeyPair mKeyPair;
+
+ @NonNull
+ private volatile String mDeviceName = "Unknown Device";
+
+ /**
+ * Specifies whether this connection has already sent a signed token.
+ */
+ private volatile boolean mSentSignature;
+
+ /**
+ * A hash map of our opened streams indexed by local ID.
+ */
+ @NonNull
+ private final ConcurrentHashMap<Integer, AdbStream> mOpenedStreams;
+
+ private volatile boolean mIsTls = false;
+
+ @GuardedBy("lock")
+ @NonNull
+ 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
+ */
+ @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);
+ }
+
+ /**
+ * 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)
+ 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.mKeyPair = Objects.requireNonNull(keyPair);
+ try {
+ this.mSocket = new Socket(host, port);
+ } catch (Throwable th) {
+ //noinspection UnnecessaryInitCause
+ throw (IOException) new IOException().initCause(th);
+ }
+ this.mPlainInputStream = mSocket.getInputStream();
+ this.mPlainOutputStream = mSocket.getOutputStream();
+
+ // Disable Nagle because we're sending tiny packets
+ mSocket.setTcpNoDelay(true);
+
+ this.mOpenedStreams = new ConcurrentHashMap<>();
+ this.mLastLocalId = 0;
+ this.mConnectionThread = createConnectionThread();
+ }
+
+ @GuardedBy("lock")
+ @NonNull
+ private InputStream getInputStream() {
+ return mIsTls ? Objects.requireNonNull(mTlsInputStream) : mPlainInputStream;
+ }
+
+ @GuardedBy("lock")
+ @NonNull
+ private OutputStream getOutputStream() {
+ return mIsTls ? Objects.requireNonNull(mTlsOutputStream) : mPlainOutputStream;
+ }
+
+ /**
+ * Creates a new connection thread.
+ *
+ * @return A new connection thread.
+ */
+ @NonNull
+ private Thread createConnectionThread() {
+ return new Thread(() -> {
+ loop:
+ while (!mConnectionThread.isInterrupted()) {
+ try {
+ // Read and parse a message off the socket's input stream
+ AdbProtocol.Message msg = AdbProtocol.Message.parse(getInputStream(), mProtocolVersion, mMaxData);
+
+ switch (msg.command) {
+ // Stream-oriented commands
+ case AdbProtocol.A_OKAY:
+ case AdbProtocol.A_WRTE:
+ case AdbProtocol.A_CLSE: {
+ // Ignore all packets when not connected
+ if (!mConnectionEstablished) {
+ continue;
+ }
+
+ // Get the stream object corresponding to the packet
+ AdbStream waitingStream = mOpenedStreams.get(msg.arg1);
+ if (waitingStream == null) {
+ continue;
+ }
+
+ synchronized (waitingStream) {
+ if (msg.command == AdbProtocol.A_OKAY) {
+ // We're ready for writes
+ waitingStream.updateRemoteId(msg.arg0);
+ waitingStream.readyForWrite();
+
+ // Notify an open/write
+ waitingStream.notify();
+ } else if (msg.command == AdbProtocol.A_WRTE) {
+ // 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) {
+ mOpenedStreams.remove(msg.arg1);
+ // Notify readers and writers
+ waitingStream.notifyClose(true);
+ }
+ }
+ 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;
+ }
+ }
+ 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;
+ }
+
+ // 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;
+ }
+
+ // Write the AUTH reply
+ sendPacket(packet);
+ break;
+ }
+ case AdbProtocol.A_CNXN: {
+ synchronized (AdbConnection.this) {
+ mProtocolVersion = msg.arg0;
+ mMaxData = msg.arg1;
+ 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;
+ }
+ } catch (Exception e) {
+ mConnectionException = e;
+ e.printStackTrace();
+ // The cleanup is taken care of by a combination of this thread and close()
+ break;
+ }
+ }
+
+ // This thread takes care of cleaning up pending streams
+ synchronized (AdbConnection.this) {
+ cleanupStreams();
+ AdbConnection.this.notifyAll();
+ mConnectionEstablished = false;
+ mConnectAttempted = false;
+ }
+ });
+ }
+
+ /**
+ * 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.
+ */
+ public boolean isConnectionEstablished() {
+ 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);
+ }
+
+ /**
+ * 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 boolean connect(long timeout, @NonNull TimeUnit unit, boolean throwOnUnauthorised)
+ throws IOException, InterruptedException, AdbAuthenticationFailedException, AdbPairingRequiredException {
+ if (mConnectionEstablished) {
+ throw new IllegalStateException("Already connected");
+ }
+
+ // Send CONNECT
+ sendPacket(AdbProtocol.generateConnect(mApi));
+
+ // Start the connection thread to respond to the peer
+ mConnectAttempted = true;
+ mAbortOnUnauthorised = throwOnUnauthorised;
+ mAuthorisationFailed = false;
+ 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 {
+ int localId = ++mLastLocalId;
+
+ if (!mConnectAttempted) {
+ throw new IllegalStateException("connect() must be called first");
+ }
+
+ waitForConnection(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+
+ // Add this stream to this list of half-open streams
+ AdbStream stream = new AdbStream(this, localId);
+ mOpenedStreams.put(localId, stream);
+
+ // Send OPEN
+ sendPacket(AdbProtocol.generateOpen(localId, Objects.requireNonNull(destination)));
+
+ // Wait for the connection thread to receive the OKAY
+ synchronized (stream) {
+ stream.wait();
+ }
+
+ // Check if the OPEN request was rejected
+ if (stream.isClosed()) {
+ mOpenedStreams.remove(localId);
+ throw new ConnectException("Stream open actively rejected by remote peer.");
+ }
+
+ return stream;
+ }
+
+ private boolean waitForConnection(long timeout, @NonNull TimeUnit unit)
+ throws InterruptedException, IOException, AdbPairingRequiredException {
+ 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());
+ }
+
+ 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");
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * This function terminates all I/O on streams associated with this ADB connection
+ */
+ private void cleanupStreams() {
+ // Close all streams on this connection
+ for (AdbStream s : mOpenedStreams.values()) {
+ try {
+ s.close();
+ } catch (IOException ignored) {
+ }
+ }
+ mOpenedStreams.clear();
+ }
+
+ /**
+ * This routine closes the Adb connection and underlying socket
+ *
+ * @throws IOException if the socket fails to close
+ */
+ @Override
+ public void close() throws IOException {
+ // Closing the socket will kick the connection thread
+ mSocket.close();
+
+ // Wait for the connection thread to die
+ mConnectionThread.interrupt();
+ try {
+ mConnectionThread.join();
+ } catch (InterruptedException ignored) {
+ }
+
+ // Destroy keypair
+ try {
+ mKeyPair.destroy();
+ } catch (DestroyFailedException ignore) {
+ }
+ }
+
+ void sendPacket(byte[] packet) throws IOException {
+ synchronized (mLock) {
+ OutputStream os = getOutputStream();
+ os.write(packet);
+ os.flush();
+ }
+ }
+
+ 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 PrivateKey mPrivateKey;
+ private Certificate mCertificate;
+ private KeyPair mKeyPair;
+ private String mDeviceName;
+
+ public Builder() {
+ }
+
+ public Builder(String host, int port) {
+ mHost = host;
+ mPort = port;
+ }
+
+ /**
+ * 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) {
+ this.mPrivateKey = privateKey;
+ return this;
+ }
+
+ /**
+ * Set public key wrapped around a certificate
+ */
+ public Builder setCertificate(Certificate certificate) {
+ this.mCertificate = certificate;
+ 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.");
+ }
+ return adbConnection;
+ }
+ }
+}
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
new file mode 100644
index 0000000..6d0676f
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
+
+package io.github.muntashirakon.adb;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class AdbInputStream extends InputStream {
+ public AdbStream mAdbStream;
+
+ public AdbInputStream(AdbStream adbStream) {
+ this.mAdbStream = adbStream;
+ }
+
+ @Override
+ public int read() throws IOException {
+ byte[] bytes = new byte[1];
+ if (read(bytes) == -1) {
+ return -1;
+ }
+ return bytes[0];
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (mAdbStream.isClosed()) return -1;
+ return mAdbStream.read(b, off, len);
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mAdbStream.available();
+ }
+}
diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbOutputStream.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbOutputStream.java
new file mode 100644
index 0000000..1894735
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbOutputStream.java
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
+
+package io.github.muntashirakon.adb;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class AdbOutputStream extends OutputStream {
+ private final AdbStream mAdbStream;
+
+ public AdbOutputStream(AdbStream adbStream) {
+ this.mAdbStream = adbStream;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ write(new byte[]{(byte) (b & 0xFF)});
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ mAdbStream.write(b, off, len);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ mAdbStream.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ flush();
+ }
+}
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
new file mode 100644
index 0000000..f324cb0
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbPairingRequiredException.java
@@ -0,0 +1,7 @@
+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
new file mode 100644
index 0000000..4c58206
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java
@@ -0,0 +1,497 @@
+// SPDX-License-Identifier: BSD-3-Clause AND (GPL-3.0-or-later OR Apache-2.0)
+
+package io.github.muntashirakon.adb;
+
+import android.os.Build;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * This class provides useful functions and fields for ADB protocol details.
+ */
+// Copyright 2013 Cameron Gutman
+final class AdbProtocol {
+ /**
+ * The length of the ADB message header
+ */
+ 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;
+
+ /**
+ * The payload sent with the CONNECT message.
+ */
+ public static final byte[] SYSTEM_IDENTITY_STRING_HOST = StringCompat.getBytes("host::\0", "UTF-8");
+
+ /**
+ * AUTH is the authentication message. It is part of the RSA public key authentication added in Android 4.2.2
+ * ({@link Build.VERSION_CODES#JELLY_BEAN_MR1}).
+ */
+ public static final int A_AUTH = 0x48545541;
+
+ /**
+ * OPEN is the open stream message. It is sent to open a new stream on the target device.
+ */
+ public static final int A_OPEN = 0x4e45504f;
+
+ /**
+ * OKAY is a success message. It is sent when a write is processed successfully.
+ */
+ public static final int A_OKAY = 0x59414b4f;
+
+ /**
+ * CLSE is the close stream message. It is sent to close an existing stream on the target device.
+ */
+ public static final int A_CLSE = 0x45534c43;
+
+ /**
+ * WRTE is the write stream message. It is sent with a payload that is the data to write to the stream.
+ */
+ public static final int A_WRTE = 0x45545257;
+
+ /**
+ * STLS is the Stream-based TLS1.3 authentication method, added in Android 9 ({@link Build.VERSION_CODES#P}).
+ */
+ 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})
+ private @interface Command {
+ }
+
+ /**
+ * Original payload size
+ */
+ public static final int MAX_PAYLOAD_V1 = 4 * 1024;
+ /**
+ * Supported payload size since Android 7 (N)
+ */
+ public static final int MAX_PAYLOAD_V2 = 256 * 1024;
+ /**
+ * Supported payload size since Android 9 (P)
+ */
+ 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;
+ /**
+ * 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;
+ }
+ if (api >= Build.VERSION_CODES.N) {
+ return MAX_PAYLOAD_V2;
+ }
+ return MAX_PAYLOAD_V1;
+ }
+
+ public static int getProtocolVersion(int api) {
+ if (api >= Build.VERSION_CODES.P) {
+ return A_VERSION_SKIP_CHECKSUM;
+ }
+ return A_VERSION_MIN;
+ }
+
+ /**
+ * This function performs a checksum on the ADB payload data.
+ *
+ * @param data The data
+ * @return The checksum of the data
+ */
+ private static int getPayloadChecksum(@NonNull byte[] data) {
+ return getPayloadChecksum(data, 0, data.length);
+ }
+
+ /**
+ * This function performs a checksum on the ADB payload data.
+ *
+ * @param data The data
+ * @param offset The start offset in the data
+ * @param length The number of bytes to take from the data
+ * @return The checksum of the data
+ */
+ private static int getPayloadChecksum(@NonNull byte[] data, int offset, int length) {
+ int checksum = 0;
+ for (int i = offset; i < offset + length; ++i) {
+ checksum += data[i] & 0xFF;
+ }
+ return checksum;
+ }
+
+ /**
+ * This function generates an ADB message given the fields.
+ *
+ * @param command Command identifier constant
+ * @param arg0 First argument
+ * @param arg1 Second argument
+ * @param data The data
+ * @return Byte array containing the message
+ */
+ @NonNull
+ public static byte[] generateMessage(@Command int command, int arg0, int arg1, @Nullable byte[] data) {
+ return generateMessage(command, arg0, arg1, data, 0, data == null ? 0 : data.length);
+ }
+
+ /**
+ * This function generates an ADB message given the fields.
+ *
+ * @param command Command identifier constant
+ * @param arg0 First argument
+ * @param arg1 Second argument
+ * @param data The data
+ * @param offset The start offset in the data
+ * @param length The number of bytes to take from the data
+ * @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
+ // struct message {
+ // unsigned command; // command identifier constant
+ // unsigned arg0; // first argument
+ // unsigned arg1; // second argument
+ // unsigned data_length; // length of payload (0 is allowed)
+ // unsigned data_check; // checksum of data payload
+ // unsigned magic; // command ^ 0xffffffff
+ // };
+
+ ByteBuffer message;
+
+ if (data != null) {
+ message = ByteBuffer.allocate(ADB_HEADER_LENGTH + length).order(ByteOrder.LITTLE_ENDIAN);
+ } else {
+ message = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ message.putInt(command);
+ message.putInt(arg0);
+ message.putInt(arg1);
+
+ if (data != null) {
+ message.putInt(length);
+ message.putInt(getPayloadChecksum(data, offset, length));
+ } else {
+ message.putInt(0);
+ message.putInt(0);
+ }
+
+ message.putInt(~command);
+
+ if (data != null) {
+ message.put(data, offset, length);
+ }
+
+ return message.array();
+ }
+
+ /**
+ * Generates a CONNECT message for a given API.
+ * <p>
+ * CONNECT(version, maxdata, "system-identity-string")
+ *
+ * @param api API version
+ * @return Byte array containing the message
+ */
+ @NonNull
+ public static byte[] generateConnect(int api) {
+ return generateMessage(A_CNXN, getProtocolVersion(api), getMaxData(api), SYSTEM_IDENTITY_STRING_HOST);
+ }
+
+ /**
+ * 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, "")
+ *
+ * @return Byte array containing the message
+ */
+ @NonNull
+ public static byte[] generateStls() {
+ return generateMessage(A_STLS, A_STLS_VERSION, 0, null);
+ }
+
+ /**
+ * Generates an OPEN stream message with the specified local ID and destination.
+ * <p>
+ * OPEN(local-id, 0, "destination")
+ *
+ * @param localId A unique local ID identifying the stream
+ * @param destination The destination of the stream on the target
+ * @return Byte array containing the message
+ */
+ @NonNull
+ public static byte[] generateOpen(int localId, @NonNull String destination) {
+ ByteBuffer bbuf = ByteBuffer.allocate(destination.length() + 1);
+ bbuf.put(StringCompat.getBytes(destination, "UTF-8"));
+ bbuf.put((byte) 0);
+ return generateMessage(A_OPEN, localId, 0, bbuf.array());
+ }
+
+ /**
+ * Generates a WRITE stream message with the specified IDs and payload.
+ * <p>
+ * WRITE(local-id, remote-id, "data")
+ *
+ * @param localId The unique local ID of the stream
+ * @param remoteId The unique remote ID of the stream
+ * @param data The data
+ * @param offset The start offset in the data
+ * @param length The number of bytes to take from the data
+ * @return Byte array containing the message
+ */
+ @NonNull
+ public static byte[] generateWrite(int localId, int remoteId, byte[] data, int offset, int length) {
+ return generateMessage(A_WRTE, localId, remoteId, data, offset, length);
+ }
+
+ /**
+ * Generates a CLOSE stream message with the specified IDs.
+ * <p>
+ * CLOSE(local-id, remote-id, "")
+ *
+ * @param localId The unique local ID of the stream
+ * @param remoteId The unique remote ID of the stream
+ * @return Byte array containing the message
+ */
+ @NonNull
+ public static byte[] generateClose(int localId, int remoteId) {
+ return generateMessage(A_CLSE, localId, remoteId, null);
+ }
+
+ /**
+ * Generates an OKAY/READY message with the specified IDs.
+ * <p>
+ * READY(local-id, remote-id, "")
+ *
+ * @param localId The unique local ID of the stream
+ * @param remoteId The unique remote ID of the stream
+ * @return Byte array containing the message
+ */
+ @NonNull
+ public static byte[] generateReady(int localId, int remoteId) {
+ return generateMessage(A_OKAY, localId, remoteId, null);
+ }
+
+ /**
+ * This class provides an abstraction for the ADB message format.
+ */
+ static final class Message {
+ /**
+ * The command field of the message
+ */
+ @Command
+ public final int command;
+ /**
+ * The arg0 field of the message
+ */
+ public final int arg0;
+ /**
+ * The arg1 field of the message
+ */
+ public final int arg1;
+ /**
+ * The payload length field of the message
+ */
+ public final int dataLength;
+ /**
+ * The checksum field of the message
+ */
+ public final int dataCheck;
+ /**
+ * The magic field of the message
+ */
+ public final int magic;
+ /**
+ * The payload of the message
+ */
+ public byte[] payload;
+
+ /**
+ * Read and parse an ADB message from the supplied input stream.
+ * <p>
+ * <b>Note:</b> If data is corrupted, the connection has to be closed immediately to avoid inconsistencies.
+ *
+ * @param in InputStream object to read data from
+ * @return An AdbMessage object represented the message read
+ * @throws IOException If the stream fails while reading.
+ * @throws StreamCorruptedException If data is corrupted.
+ */
+ @NonNull
+ public static Message parse(@NonNull InputStream in, int protocolVersion, int maxData) throws IOException {
+ 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);
+
+ Message msg = new Message(header);
+
+ // Validate header
+ 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
+ && 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));
+ }
+
+ if (msg.dataLength == 0) {
+ // No payload supplied, return immediately
+ return msg;
+ }
+
+ // 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);
+
+ // Verify payload
+ if ((protocolVersion <= A_VERSION_MIN || (msg.command == A_CNXN && msg.arg0 <= A_VERSION_MIN))
+ && getPayloadChecksum(msg.payload) != msg.dataCheck) {
+ // Checksum verification failed
+ throw new StreamCorruptedException("Invalid header: Checksum mismatched.");
+ }
+
+ return msg;
+ }
+
+ private Message(@NonNull ByteBuffer header) {
+ command = header.getInt();
+ arg0 = header.getInt();
+ arg1 = header.getInt();
+ dataLength = header.getInt();
+ dataCheck = header.getInt();
+ magic = header.getInt();
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ String tag;
+ switch (command) {
+ case A_SYNC:
+ tag = "SYNC";
+ break;
+ case A_CNXN:
+ tag = "CNXN";
+ break;
+ case A_OPEN:
+ tag = "OPEN";
+ break;
+ case A_OKAY:
+ tag = "OKAY";
+ break;
+ case A_CLSE:
+ tag = "CLSE";
+ break;
+ case A_WRTE:
+ tag = "WRTE";
+ break;
+ case A_AUTH:
+ tag = "AUTH";
+ break;
+ case A_STLS:
+ tag = "STLS";
+ break;
+ default:
+ tag = "????";
+ break;
+ }
+ return "Message{" +
+ "command=" + tag +
+ ", arg0=0x" + Integer.toHexString(arg0) +
+ ", arg1=0x" + Integer.toHexString(arg1) +
+ ", payloadLength=" + dataLength +
+ ", checksum=" + dataCheck +
+ ", magic=0x" + Integer.toHexString(magic) +
+ ", payload=" + Arrays.toString(payload) +
+ '}';
+ }
+ }
+
+}
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
new file mode 100644
index 0000000..6d24c06
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: BSD-3-Clause AND (GPL-3.0-or-later OR Apache-2.0)
+
+package io.github.muntashirakon.adb;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class abstracts the underlying ADB streams
+ */
+// Copyright 2013 Cameron Gutman
+public class AdbStream implements Closeable {
+
+ /**
+ * The AdbConnection object that the stream communicates over
+ */
+ private final AdbConnection mAdbConnection;
+
+ /**
+ * The local ID of the stream
+ */
+ private final int mLocalId;
+
+ /**
+ * The remote ID of the stream
+ */
+ private volatile int mRemoteId;
+
+ /**
+ * Indicates whether WRTE is currently allowed
+ */
+ private final AtomicBoolean mWriteReady;
+
+ /**
+ * A queue of data from the target's WRTE packets
+ */
+ private final Queue<byte[]> mReadQueue;
+
+ /**
+ * Store data received from the first WRTE packet in order to support buffering.
+ */
+ private final ByteBuffer mReadBuffer;
+
+ /**
+ * Indicates whether the connection is closed already
+ */
+ private volatile boolean mIsClosed;
+
+ /**
+ * Whether the remote peer has closed but we still have unread data in the queue
+ */
+ private volatile boolean mPendingClose;
+
+ /**
+ * Creates a new AdbStream object on the specified AdbConnection
+ * with the given local ID.
+ *
+ * @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;
+ this.mLocalId = localId;
+ this.mReadQueue = new ConcurrentLinkedQueue<>();
+ this.mReadBuffer = (ByteBuffer) ByteBuffer.allocate(adbConnection.getMaxData()).flip();
+ this.mWriteReady = new AtomicBoolean(false);
+ this.mIsClosed = false;
+ }
+
+ public AdbInputStream openInputStream() {
+ return new AdbInputStream(this);
+ }
+
+ public AdbOutputStream openOutputStream() {
+ return new AdbOutputStream(this);
+ }
+
+ /**
+ * Called by the connection thread to indicate newly received data.
+ *
+ * @param payload Data inside the WRTE message
+ */
+ void addPayload(byte[] payload) {
+ synchronized (mReadQueue) {
+ mReadQueue.add(payload);
+ mReadQueue.notifyAll();
+ }
+ }
+
+ /**
+ * Called by the connection thread to send an OKAY packet, allowing the
+ * other side to continue transmission.
+ *
+ * @throws IOException If the connection fails while sending the packet
+ */
+ void sendReady() throws IOException {
+ // Generate and send a OKAY packet
+ mAdbConnection.sendPacket(AdbProtocol.generateReady(mLocalId, mRemoteId));
+ }
+
+ /**
+ * Called by the connection thread to update the remote ID for this stream
+ *
+ * @param remoteId New remote ID
+ */
+ void updateRemoteId(int remoteId) {
+ this.mRemoteId = remoteId;
+ }
+
+ /**
+ * Called by the connection thread to indicate the stream is okay to send data.
+ */
+ void readyForWrite() {
+ mWriteReady.set(true);
+ }
+
+ /**
+ * Called by the connection thread to notify that the stream was closed by the peer.
+ */
+ void notifyClose(boolean closedByPeer) {
+ // We don't call close() because it sends another CLSE
+ if (closedByPeer && !mReadQueue.isEmpty()) {
+ // The remote peer closed the stream, but we haven't finished reading the remaining data
+ mPendingClose = true;
+ } else {
+ mIsClosed = true;
+ }
+
+ // Notify readers and writers
+ synchronized (this) {
+ notifyAll();
+ }
+ synchronized (mReadQueue) {
+ mReadQueue.notifyAll();
+ }
+ }
+
+ /**
+ * Read bytes from the ADB daemon.
+ *
+ * @return the next byte of data, or {@code -1} if the end of the stream is reached.
+ * @throws IOException If the stream fails while waiting
+ */
+ public int read(byte[] bytes, int offset, int length) throws IOException {
+ if (mReadBuffer.hasRemaining()) {
+ return readBuffer(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);
+ }
+ }
+ // Add data to the buffer
+ if (data != null) {
+ mReadBuffer.clear();
+ mReadBuffer.put(data);
+ mReadBuffer.flip();
+ if (mReadBuffer.hasRemaining()) {
+ return readBuffer(bytes, offset, length);
+ }
+ }
+
+ if (mIsClosed) {
+ throw new IOException("Stream closed.");
+ }
+
+ if (mPendingClose && mReadQueue.isEmpty()) {
+ // The peer closed the stream, and we've finished reading the stream data, so this stream is finished
+ mIsClosed = true;
+ }
+ }
+
+ return -1;
+ }
+
+ private int readBuffer(byte[] bytes, int offset, int length) {
+ int count = 0;
+ for (int i = offset; i < offset + length; ++i) {
+ if (mReadBuffer.hasRemaining()) {
+ bytes[i] = mReadBuffer.get();
+ ++count;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Sends a WRTE packet with a given byte array payload. It does not flush the stream.
+ *
+ * @param bytes Payload in the form of a byte array
+ * @throws IOException If the stream fails while sending data
+ */
+ public void write(byte[] bytes, int offset, int length) 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);
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ public void flush() throws IOException {
+ if (mIsClosed) {
+ throw new IOException("Stream closed");
+ }
+ mAdbConnection.flushPacket();
+ }
+
+ /**
+ * Closes the stream. This sends a close message to the peer.
+ *
+ * @throws IOException If the stream fails while sending the close message.
+ */
+ @Override
+ public void close() throws IOException {
+ synchronized (this) {
+ // This may already be closed by the remote host
+ if (mIsClosed)
+ return;
+
+ // Notify readers/writers that we've closed
+ notifyClose(false);
+ }
+
+ mAdbConnection.sendPacket(AdbProtocol.generateClose(mLocalId, mRemoteId));
+ }
+
+ /**
+ * Returns whether the stream is closed or not
+ *
+ * @return True if the stream is close, false if not
+ */
+ public boolean isClosed() {
+ return mIsClosed;
+ }
+
+ /**
+ * Returns an estimate of available data.
+ *
+ * @return an estimate of the number of bytes that can be read from this stream without blocking.
+ * @throws IOException if the stream is close.
+ */
+ public int available() throws IOException {
+ synchronized (this) {
+ if (mIsClosed) {
+ throw new IOException("Stream closed.");
+ }
+ if (mReadBuffer.hasRemaining()) {
+ return mReadBuffer.remaining();
+ }
+ 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
new file mode 100644
index 0000000..6aa12aa
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
+
+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.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.GeneralSecurityException;
+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;
+
+final class AndroidPubkey {
+ /**
+ * Size of an RSA modulus such as an encrypted block or a signature.
+ */
+ public static final int ANDROID_PUBKEY_MODULUS_SIZE = 2048 / 8;
+
+ /**
+ * Size of an encoded RSA key.
+ */
+ public static final int ANDROID_PUBKEY_ENCODED_SIZE = 3 * 4 + 2 * ANDROID_PUBKEY_MODULUS_SIZE;
+
+ /**
+ * Size of the RSA modulus in words.
+ */
+ 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
+ * @param name Name without null terminator
+ * @return Byte array containing the converted RSAPublicKey object
+ */
+ @NonNull
+ 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();
+ }
+ }
+
+ // 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");
+ }
+
+ // https://android.googlesource.com/platform/system/core/+/e797a5c75afc17024d0f0f488c130128fcd704e2/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
+ // uint8_t modulus[ANDROID_PUBKEY_MODULUS_SIZE]; // RSA modulus as a little-endian array.
+ // uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE]; // Montgomery parameter R^2 as a little-endian array.
+ // uint32_t exponent; // RSA modulus: 3 or 65537
+ // } 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
+ * {@link #ANDROID_PUBKEY_ENCODED_SIZE}
+ */
+ @NonNull
+ public static byte[] encode(@NonNull RSAPublicKey publicKey) throws InvalidKeyException {
+ BigInteger r32;
+ BigInteger n0inv;
+ BigInteger rr;
+
+ if (publicKey.getModulus().toByteArray().length < ANDROID_PUBKEY_MODULUS_SIZE) {
+ throw new InvalidKeyException("Invalid key length " + publicKey.getModulus().toByteArray().length);
+ }
+
+ ByteBuffer keyStruct = ByteBuffer.allocate(ANDROID_PUBKEY_ENCODED_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ // Store the modulus size.
+ keyStruct.putInt(ANDROID_PUBKEY_MODULUS_SIZE_WORDS); // modulus_size_words
+
+ // Compute and store n0inv = -1 / N[0] mod 2^32.
+ r32 = BigInteger.ZERO.setBit(32); // r32 = 2^32
+ n0inv = publicKey.getModulus().mod(r32); // n0inv = N[0] mod 2^32
+ n0inv = n0inv.modInverse(r32); // n0inv = 1/n0inv mod 2^32
+ n0inv = r32.subtract(n0inv); // n0inv = 2^32 - n0inv
+ keyStruct.putInt(n0inv.intValue()); // n0inv
+
+ // Store the modulus.
+ keyStruct.put(Objects.requireNonNull(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)));
+
+ // Store the exponent.
+ keyStruct.putInt(publicKey.getPublicExponent().intValue()); // exponent
+
+ return keyStruct.array();
+ }
+
+ @Nullable
+ private static byte[] BigEndianToLittleEndianPadded(int len, @NonNull BigInteger in) {
+ 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;
+ }
+ num_bytes = len;
+ }
+ System.arraycopy(bytes, 0, out, 0, num_bytes);
+ return out;
+ }
+
+ static boolean fitsInBytes(@NonNull byte[] bytes, int num_bytes, int len) {
+ byte mask = 0;
+ for (int i = len; i < num_bytes; i++) {
+ mask |= bytes[i];
+ }
+ return mask == 0;
+ }
+
+ @NonNull
+ private static byte[] swapEndianness(@NonNull byte[] bytes) {
+ int len = bytes.length;
+ byte[] out = new byte[len];
+ for (int i = 0; i < len; ++i) {
+ out[i] = bytes[len - i - 1];
+ }
+ return out;
+ }
+}
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
new file mode 100644
index 0000000..55cc1bf
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/ByteArrayNoThrowOutputStream.java
@@ -0,0 +1,24 @@
+// 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
new file mode 100644
index 0000000..4574d8b
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
+
+package io.github.muntashirakon.adb;
+
+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;
+
+ public KeyPair(PrivateKey privateKey, Certificate certificate) {
+ mPrivateKey = privateKey;
+ mCertificate = certificate;
+ }
+
+ public PrivateKey getPrivateKey() {
+ return mPrivateKey;
+ }
+
+ public PublicKey getPublicKey() {
+ return mCertificate.getPublicKey();
+ }
+
+ 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
new file mode 100644
index 0000000..523bce3
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/LocalServices.java
@@ -0,0 +1,271 @@
+// 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
new file mode 100644
index 0000000..fe6343f
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PRNGFixes.java
@@ -0,0 +1,319 @@
+// 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
new file mode 100644
index 0000000..a329c29
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java
@@ -0,0 +1,131 @@
+// 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 androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
+
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.modes.GCMModeCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.HKDFParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+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");
+
+ // 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 int HKDF_KEY_LENGTH = 128 / 8;
+ public static final int GCM_IV_LENGTH = 12; // in bytes
+
+ private final byte[] mMsg;
+ private final Spake2Context mSpake2Ctx;
+ private final byte[] mSecretKey = new byte[HKDF_KEY_LENGTH];
+ private long mDecIv = 0;
+ private long mEncIv = 0;
+ private boolean mIsDestroyed = false;
+
+ @Nullable
+ public static PairingAuthCtx createAlice(byte[] password) {
+ Spake2Context spake25519 = new Spake2Context(Spake2Role.Alice, CLIENT_NAME, SERVER_NAME);
+ try {
+ return new PairingAuthCtx(spake25519, password);
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ @Nullable
+ public static PairingAuthCtx createBob(byte[] password) {
+ Spake2Context spake25519 = new Spake2Context(Spake2Role.Bob, SERVER_NAME, CLIENT_NAME);
+ try {
+ return new PairingAuthCtx(spake25519, password);
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ return null;
+ }
+ }
+
+ private PairingAuthCtx(Spake2Context spake25519, byte[] password)
+ throws IllegalArgumentException, IllegalStateException {
+ mSpake2Ctx = spake25519;
+ mMsg = mSpake2Ctx.generateMessage(password);
+ }
+
+ public byte[] getMsg() {
+ return mMsg;
+ }
+
+ public boolean initCipher(byte[] theirMsg) throws IllegalArgumentException, IllegalStateException {
+ 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;
+ }
+
+ @Nullable
+ public byte[] encrypt(@NonNull byte[] in) {
+ return encryptDecrypt(true, in, ByteBuffer.allocate(GCM_IV_LENGTH)
+ .order(ByteOrder.LITTLE_ENDIAN).putLong(mEncIv++).array());
+ }
+
+ @Nullable
+ public byte[] decrypt(@NonNull byte[] in) {
+ return encryptDecrypt(false, in, ByteBuffer.allocate(GCM_IV_LENGTH)
+ .order(ByteOrder.LITTLE_ENDIAN).putLong(mDecIv++).array());
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mIsDestroyed;
+ }
+
+ @Override
+ public void destroy() {
+ mIsDestroyed = true;
+ Arrays.fill(mSecretKey, (byte) 0);
+ mSpake2Ctx.destroy();
+ }
+
+ @Nullable
+ private byte[] encryptDecrypt(boolean forEncryption, @NonNull byte[] in, @NonNull byte[] iv) {
+ if (mIsDestroyed) return null;
+ AEADParameters spec = new AEADParameters(new KeyParameter(mSecretKey), mSecretKey.length * 8, iv);
+ GCMModeCipher cipher = GCMBlockCipher.newInstance(AESEngine.newInstance());
+ cipher.init(forEncryption, spec);
+ byte[] out = new byte[cipher.getOutputSize(in.length)];
+ int newOffset = cipher.processBytes(in, 0, in.length, out, 0);
+ try {
+ cipher.doFinal(out, newOffset);
+ } catch (InvalidCipherTextException e) {
+ return null;
+ }
+ return out;
+ }
+}
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
new file mode 100644
index 0000000..732a08e
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
+
+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 java.io.Closeable;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.InvalidKeyException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.interfaces.RSAPublicKey;
+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
+// 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 EXPORTED_KEY_LABEL = "adb-label\u0000";
+ public static final int EXPORT_KEY_SIZE = 64;
+
+ private enum State {
+ Ready,
+ ExchangingMsgs,
+ ExchangingPeerInfo,
+ 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 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);
+ this.mPort = port;
+ this.mPswd = Objects.requireNonNull(pswd);
+ this.mPeerInfo = new PeerInfo(PeerInfo.ADB_RSA_PUB_KEY, AndroidPubkey.encodeWithName((RSAPublicKey)
+ keyPair.getPublicKey(), Objects.requireNonNull(deviceName)));
+ this.mSslContext = SslUtils.getSslContext(keyPair);
+ }
+
+ public PairingConnectionCtx(@NonNull String host, int port, @NonNull byte[] pswd, @NonNull PrivateKey privateKey,
+ @NonNull Certificate certificate, @NonNull String deviceName)
+ throws NoSuchAlgorithmException, KeyManagementException, InvalidKeyException {
+ this(host, port, pswd, new KeyPair(Objects.requireNonNull(privateKey), Objects.requireNonNull(certificate)),
+ deviceName);
+ }
+
+ public void start() throws IOException {
+ if (mState != State.Ready) {
+ throw new IOException("Connection is not ready yet.");
+ }
+
+ mState = State.ExchangingMsgs;
+
+ // Start worker
+ setupTlsConnection();
+
+ for (; ; ) {
+ switch (mState) {
+ case ExchangingMsgs:
+ if (!doExchangeMsgs()) {
+ notifyResult();
+ throw new IOException("Exchanging message wasn't successful.");
+ }
+ mState = State.ExchangingPeerInfo;
+ break;
+ case ExchangingPeerInfo:
+ if (!doExchangePeerInfo()) {
+ notifyResult();
+ throw new IOException("Could not exchange peer info.");
+ }
+ notifyResult();
+ return;
+ case Ready:
+ case Stopped:
+ throw new IOException("Connection closed with errors.");
+ }
+ }
+ }
+
+ private void notifyResult() {
+ mState = State.Stopped;
+ }
+
+ 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.setTcpNoDelay(true);
+
+ // We use custom SSLContext to allow any SSL certificates
+ SSLSocket sslSocket = (SSLSocket) mSslContext.getSocketFactory().createSocket(socket, mHost, mPort, true);
+ sslSocket.startHandshake();
+ Log.d(TAG, "Handshake succeeded.");
+
+ mInputStream = new DataInputStream(sslSocket.getInputStream());
+ mOutputStream = new DataOutputStream(sslSocket.getOutputStream());
+
+ // 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[] 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
+ 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");
+ }
+ 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);
+ }
+ }
+
+ private void writeHeader(@NonNull PairingPacketHeader header, @NonNull byte[] payload) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(PairingPacketHeader.PAIRING_PACKET_HEADER_SIZE)
+ .order(ByteOrder.BIG_ENDIAN);
+ header.writeTo(buffer);
+
+ mOutputStream.write(buffer.array());
+ mOutputStream.write(payload);
+ }
+
+ @Nullable
+ private PairingPacketHeader readHeader() throws IOException {
+ byte[] bytes = new byte[PairingPacketHeader.PAIRING_PACKET_HEADER_SIZE];
+ mInputStream.readFully(bytes);
+ ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+ return PairingPacketHeader.readFrom(buffer);
+ }
+
+ @NonNull
+ private PairingPacketHeader createHeader(byte type, int payloadSize) {
+ return new PairingPacketHeader(PairingPacketHeader.CURRENT_KEY_HEADER_VERSION, type, payloadSize);
+ }
+
+ private boolean checkHeaderType(byte expected, byte actual) {
+ if (expected != actual) {
+ Log.e(TAG, "Unexpected header type (expected=" + expected + " actual=" + actual + ")");
+ return false;
+ }
+ return true;
+ }
+
+ private boolean doExchangeMsgs() throws IOException {
+ byte[] msg = mPairingAuthCtx.getMsg();
+
+ PairingPacketHeader ourHeader = createHeader(PairingPacketHeader.SPAKE2_MSG, msg.length);
+ // Write our SPAKE2 msg
+ writeHeader(ourHeader, msg);
+
+ // Read the peer's SPAKE2 msg header
+ PairingPacketHeader theirHeader = readHeader();
+ if (theirHeader == null || !checkHeaderType(PairingPacketHeader.SPAKE2_MSG, theirHeader.type)) return false;
+
+ // Read the SPAKE2 msg payload and initialize the cipher for encrypting the PeerInfo and certificate.
+ byte[] theirMsg = new byte[theirHeader.payloadSize];
+ mInputStream.readFully(theirMsg);
+
+ try {
+ return mPairingAuthCtx.initCipher(theirMsg);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to initialize pairing cipher");
+ //noinspection UnnecessaryInitCause
+ throw (IOException) new IOException().initCause(e);
+ }
+ }
+
+ private boolean doExchangePeerInfo() throws IOException {
+ // Encrypt PeerInfo
+ ByteBuffer buffer = ByteBuffer.allocate(PeerInfo.MAX_PEER_INFO_SIZE).order(ByteOrder.BIG_ENDIAN);
+ mPeerInfo.writeTo(buffer);
+ byte[] outBuffer = mPairingAuthCtx.encrypt(buffer.array());
+ if (outBuffer == null) {
+ Log.e(TAG, "Failed to encrypt peer info");
+ return false;
+ }
+
+ // Write out the packet header
+ PairingPacketHeader ourHeader = createHeader(PairingPacketHeader.PEER_INFO, outBuffer.length);
+ // Write out the encrypted payload
+ writeHeader(ourHeader, outBuffer);
+
+ // Read in the peer's packet header
+ PairingPacketHeader theirHeader = readHeader();
+ if (theirHeader == null || !checkHeaderType(PairingPacketHeader.PEER_INFO, theirHeader.type)) return false;
+
+ // Read in the encrypted peer certificate
+ byte[] theirMsg = new byte[theirHeader.payloadSize];
+ mInputStream.readFully(theirMsg);
+
+ // Try to decrypt the certificate
+ byte[] decryptedMsg = mPairingAuthCtx.decrypt(theirMsg);
+ if (decryptedMsg == null) {
+ Log.e(TAG, "Unsupported payload while decrypting peer info.");
+ return false;
+ }
+
+ // The decrypted message should contain the PeerInfo.
+ if (decryptedMsg.length != PeerInfo.MAX_PEER_INFO_SIZE) {
+ Log.e(TAG, "Got size=" + decryptedMsg.length + " PeerInfo.size=" + PeerInfo.MAX_PEER_INFO_SIZE);
+ return false;
+ }
+
+ PeerInfo theirPeerInfo = PeerInfo.readFrom(ByteBuffer.wrap(decryptedMsg));
+ Log.d(TAG, theirPeerInfo.toString());
+ return true;
+ }
+
+ @Override
+ public void close() {
+ Arrays.fill(mPswd, (byte) 0);
+ try {
+ mInputStream.close();
+ } catch (IOException ignore) {
+ }
+ try {
+ mOutputStream.close();
+ } catch (IOException ignore) {
+ }
+ if (mState != State.Ready) {
+ mPairingAuthCtx.destroy();
+ }
+ }
+
+ 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;
+
+ @NonNull
+ public static PeerInfo readFrom(@NonNull ByteBuffer buffer) {
+ byte type = buffer.get();
+ byte[] data = new byte[MAX_PEER_INFO_SIZE - 1];
+ buffer.get(data);
+ return new PeerInfo(type, data);
+ }
+
+ private final byte type;
+ private final byte[] data = new byte[MAX_PEER_INFO_SIZE - 1];
+
+ public PeerInfo(byte type, byte[] data) {
+ this.type = type;
+ System.arraycopy(data, 0, this.data, 0, Math.min(data.length, MAX_PEER_INFO_SIZE - 1));
+ }
+
+ 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 {
+ public static final byte CURRENT_KEY_HEADER_VERSION = 1;
+ public static final byte MIN_SUPPORTED_KEY_HEADER_VERSION = 1;
+ public static final byte MAX_SUPPORTED_KEY_HEADER_VERSION = 1;
+
+ public static final int MAX_PAYLOAD_SIZE = 2 * PeerInfo.MAX_PEER_INFO_SIZE;
+ public static final byte PAIRING_PACKET_HEADER_SIZE = 6;
+
+ public static final byte SPAKE2_MSG = 0;
+ public static final byte PEER_INFO = 1;
+
+ @Nullable
+ public static PairingPacketHeader readFrom(@NonNull ByteBuffer buffer) {
+ byte version = buffer.get();
+ byte type = buffer.get();
+ int payload = buffer.getInt();
+ if (version < MIN_SUPPORTED_KEY_HEADER_VERSION || version > MAX_SUPPORTED_KEY_HEADER_VERSION) {
+ Log.e(TAG, "PairingPacketHeader version mismatch (us=" + CURRENT_KEY_HEADER_VERSION
+ + " them=" + version + ")");
+ return null;
+ }
+ if (type != SPAKE2_MSG && type != PEER_INFO) {
+ Log.e(TAG, "Unknown PairingPacket type " + type);
+ return null;
+ }
+ if (payload <= 0 || payload > MAX_PAYLOAD_SIZE) {
+ Log.e(TAG, "Header payload not within a safe payload size (size=" + payload + ")");
+ return null;
+ }
+ return new PairingPacketHeader(version, type, payload);
+ }
+
+ private final byte version;
+ private final byte type;
+ private final int payloadSize;
+
+ public PairingPacketHeader(byte version, byte type, int payloadSize) {
+ this.version = version;
+ this.type = type;
+ this.payloadSize = payloadSize;
+ }
+
+ 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
new file mode 100644
index 0000000..aea4e6f
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
+
+package io.github.muntashirakon.adb;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+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.X509Certificate;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+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;
+ }
+
+ @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;
+ }
+ System.out.println("Using " + (customConscrypt ? "custom" : "default") + " TLSv1.3 provider...");
+ sslContext.init(new KeyManager[]{getKeyManager(keyPair)},
+ new X509TrustManager[]{getAllAcceptingTrustManager()},
+ new SecureRandom());
+ return sslContext;
+ }
+
+ @NonNull
+ private static KeyManager getKeyManager(KeyPair keyPair) {
+ return new X509ExtendedKeyManager() {
+ private final String mAlias = "key";
+
+ @Override
+ public String[] getClientAliases(String keyType, Principal[] issuers) {
+ return null;
+ }
+
+ @Override
+ public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
+ for (String keyType : keyTypes) {
+ if (keyType.equals("RSA")) return mAlias;
+ }
+ return null;
+ }
+
+ @Override
+ public String[] getServerAliases(String keyType, Principal[] issuers) {
+ return null;
+ }
+
+ @Override
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+ return null;
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain(String alias) {
+ if (this.mAlias.equals(alias)) {
+ return new X509Certificate[]{(X509Certificate) keyPair.getCertificate()};
+ }
+ return null;
+ }
+
+ @Override
+ public PrivateKey getPrivateKey(String alias) {
+ if (this.mAlias.equals(alias)) {
+ return keyPair.getPrivateKey();
+ }
+ return null;
+ }
+ };
+ }
+
+ @SuppressLint("TrustAllX509TrustManager") // Accept all certificates
+ @NonNull
+ private static X509TrustManager getAllAcceptingTrustManager() {
+ return new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+ };
+ }
+}
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
new file mode 100644
index 0000000..1e05eee
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/StringCompat.java
@@ -0,0 +1,26 @@
+// 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
new file mode 100644
index 0000000..6b424cb
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AdbMdns.java
@@ -0,0 +1,193 @@
+// 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
new file mode 100644
index 0000000..bb679d2
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AndroidUtils.java
@@ -0,0 +1,58 @@
+// 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
new file mode 100644
index 0000000..704dd26
--- /dev/null
+++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/package.html
@@ -0,0 +1 @@
+<p>All Android dependencies are kept under this package for easy reference.</p> \ No newline at end of file