adb: Add a way to distinguish between connection failures and successes
This change adds a callback that is invoked exactly once, either when
the connection is fully established (i.e. CNXN packets have been sent
and received) or the atransport object is deleted before that (because
the connection failed).
This helps in distinguishing between successful and failing connections
for TCP. Especially when there is some kind of port
forwarding/multiplexing in between (like an SSH tunnel or SSLH proxy).
Bug: 74411879
Test: adb connect chromebook:22 (which runs an sslh tunnel to adbd).
either succeeds or fails, but not fake-succeeds.
Change-Id: I7e826c6f5d4c30338a03b2d376a857ac5d05672a
diff --git a/adb/transport.h b/adb/transport.h
index d18c362..4e0220f 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -20,6 +20,7 @@
#include <sys/types.h>
#include <atomic>
+#include <chrono>
#include <condition_variable>
#include <deque>
#include <functional>
@@ -30,6 +31,7 @@
#include <thread>
#include <unordered_set>
+#include <android-base/macros.h>
#include <android-base/thread_annotations.h>
#include <openssl/rsa.h>
@@ -160,6 +162,35 @@
usb_handle* handle_;
};
+// Waits for a transport's connection to be not pending. This is a separate
+// object so that the transport can be destroyed and another thread can be
+// notified of it in a race-free way.
+class ConnectionWaitable {
+ public:
+ ConnectionWaitable() = default;
+ ~ConnectionWaitable() = default;
+
+ // Waits until the first CNXN packet has been received by the owning
+ // atransport, or the specified timeout has elapsed. Can be called from any
+ // thread.
+ //
+ // Returns true if the CNXN packet was received in a timely fashion, false
+ // otherwise.
+ bool WaitForConnection(std::chrono::milliseconds timeout);
+
+ // Can be called from any thread when the connection stops being pending.
+ // Only the first invocation will be acknowledged, the rest will be no-ops.
+ void SetConnectionEstablished(bool success);
+
+ private:
+ bool connection_established_ GUARDED_BY(mutex_) = false;
+ bool connection_established_ready_ GUARDED_BY(mutex_) = false;
+ std::mutex mutex_;
+ std::condition_variable cv_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectionWaitable);
+};
+
class atransport {
public:
// TODO(danalbert): We expose waaaaaaay too much stuff because this was
@@ -168,13 +199,15 @@
// it's better to do this piece by piece.
atransport(ConnectionState state = kCsOffline)
- : id(NextTransportId()), connection_state_(state) {
+ : id(NextTransportId()),
+ connection_state_(state),
+ connection_waitable_(std::make_shared<ConnectionWaitable>()) {
// Initialize protocol to min version for compatibility with older versions.
// Version will be updated post-connect.
protocol_version = A_VERSION_MIN;
max_payload = MAX_PAYLOAD;
}
- virtual ~atransport() {}
+ virtual ~atransport();
int Write(apacket* p);
void Kick();
@@ -241,7 +274,14 @@
// This is to make it easier to use the same network target for both fastboot and adb.
bool MatchesTarget(const std::string& target) const;
-private:
+ // Notifies that the atransport is no longer waiting for the connection
+ // being established.
+ void SetConnectionEstablished(bool success);
+
+ // Gets a shared reference to the ConnectionWaitable.
+ std::shared_ptr<ConnectionWaitable> connection_waitable() { return connection_waitable_; }
+
+ private:
bool kicked_ = false;
// A set of features transmitted in the banner with the initial connection.
@@ -258,6 +298,10 @@
std::deque<std::shared_ptr<RSA>> keys_;
#endif
+ // A sharable object that can be used to wait for the atransport's
+ // connection to be established.
+ std::shared_ptr<ConnectionWaitable> connection_waitable_;
+
DISALLOW_COPY_AND_ASSIGN(atransport);
};