diff --git a/adb/adb.cpp b/adb/adb.cpp index a8737f5a4..180954bef 100644 --- a/adb/adb.cpp +++ b/adb/adb.cpp @@ -58,6 +58,8 @@ ADB_MUTEX_DEFINE(D_lock); #if !ADB_HOST const char* adb_device_banner = "device"; static android::base::LogdLogger gLogdLogger; +#else +const char* adb_device_banner = "host"; #endif void AdbLogger(android::base::LogId id, android::base::LogSeverity severity, @@ -305,45 +307,50 @@ static void send_close(unsigned local, unsigned remote, atransport *t) send_packet(p, t); } -static size_t fill_connect_data(char *buf, size_t bufsize) -{ -#if ADB_HOST - return snprintf(buf, bufsize, "host::") + 1; -#else - static const char *cnxn_props[] = { +std::string get_connection_string() { + std::vector connection_properties; + +#if !ADB_HOST + static const char* cnxn_props[] = { "ro.product.name", "ro.product.model", "ro.product.device", }; - static const int num_cnxn_props = ARRAY_SIZE(cnxn_props); - int i; - size_t remaining = bufsize; - size_t len; - len = snprintf(buf, remaining, "%s::", adb_device_banner); - remaining -= len; - buf += len; - for (i = 0; i < num_cnxn_props; i++) { + for (const auto& prop_name : cnxn_props) { char value[PROPERTY_VALUE_MAX]; - property_get(cnxn_props[i], value, ""); - len = snprintf(buf, remaining, "%s=%s;", cnxn_props[i], value); - remaining -= len; - buf += len; + property_get(prop_name, value, ""); + connection_properties.push_back( + android::base::StringPrintf("%s=%s", prop_name, value)); } - - return bufsize - remaining + 1; #endif + + connection_properties.push_back(android::base::StringPrintf( + "features=%s", android::base::Join(supported_features(), ',').c_str())); + + return android::base::StringPrintf( + "%s::%s", adb_device_banner, + android::base::Join(connection_properties, ';').c_str()); } -void send_connect(atransport *t) -{ +void send_connect(atransport* t) { D("Calling send_connect \n"); - apacket *cp = get_apacket(); + apacket* cp = get_apacket(); cp->msg.command = A_CNXN; cp->msg.arg0 = t->get_protocol_version(); cp->msg.arg1 = t->get_max_payload(); - cp->msg.data_length = fill_connect_data((char *)cp->data, - MAX_PAYLOAD_V1); + + std::string connection_str = get_connection_string(); + // Connect and auth packets are limited to MAX_PAYLOAD_V1 because we don't + // yet know how much data the other size is willing to accept. + if (connection_str.length() > MAX_PAYLOAD_V1) { + LOG(FATAL) << "Connection banner is too long (length = " + << connection_str.length() << ")"; + } + + memcpy(cp->data, connection_str.c_str(), connection_str.length()); + cp->msg.data_length = connection_str.length(); + send_packet(cp, t); } @@ -356,8 +363,8 @@ static void qual_overwrite(char** dst, const std::string& src) { *dst = strdup(src.c_str()); } -void parse_banner(const char* banner, atransport* t) { - D("parse_banner: %s\n", banner); +void parse_banner(const std::string& banner, atransport* t) { + D("parse_banner: %s\n", banner.c_str()); // The format is something like: // "device::ro.product.name=x;ro.product.model=y;ro.product.device=z;". @@ -380,6 +387,10 @@ void parse_banner(const char* banner, atransport* t) { qual_overwrite(&t->model, value); } else if (key == "ro.product.device") { qual_overwrite(&t->device, value); + } else if (key == "features") { + for (const auto& feature : android::base::Split(value, ",")) { + t->add_feature(feature); + } } } } @@ -407,6 +418,29 @@ void parse_banner(const char* banner, atransport* t) { } } +static void handle_new_connection(atransport* t, apacket* p) { + if (t->connection_state != kCsOffline) { + t->connection_state = kCsOffline; + handle_offline(t); + } + + t->update_version(p->msg.arg0, p->msg.arg1); + std::string banner(reinterpret_cast(p->data), + p->msg.data_length); + parse_banner(banner, t); + +#if ADB_HOST + handle_online(t); +#else + if (!auth_required) { + handle_online(t); + send_connect(t); + } else { + send_auth_request(t); + } +#endif +} + void handle_packet(apacket *p, atransport *t) { asocket *s; @@ -431,25 +465,8 @@ void handle_packet(apacket *p, atransport *t) } return; - case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */ - if(t->connection_state != kCsOffline) { - t->connection_state = kCsOffline; - handle_offline(t); - } - - t->update_version(p->msg.arg0, p->msg.arg1); - parse_banner(reinterpret_cast(p->data), t); - -#if ADB_HOST - handle_online(t); -#else - if (!auth_required) { - handle_online(t); - send_connect(t); - } else { - send_auth_request(t); - } -#endif + case A_CNXN: // CONNECT(version, maxdata, "system-id-string") + handle_new_connection(t, p); break; case A_AUTH: diff --git a/adb/adb.h b/adb/adb.h index 9ff830ee5..6855f3b0c 100644 --- a/adb/adb.h +++ b/adb/adb.h @@ -20,10 +20,10 @@ #include #include -#include - #include +#include + #include "adb_trace.h" #include "fdevent.h" @@ -191,71 +191,6 @@ enum ConnectionState { kCsUnauthorized, }; -class atransport { -public: - // TODO(danalbert): We expose waaaaaaay too much stuff because this was - // historically just a struct, but making the whole thing a more idiomatic - // class in one go is a very large change. Given how bad our testing is, - // it's better to do this piece by piece. - - atransport() { - auth_fde = {}; - transport_fde = {}; - protocol_version = A_VERSION; - max_payload = MAX_PAYLOAD; - } - - virtual ~atransport() {} - - int (*read_from_remote)(apacket* p, atransport* t) = nullptr; - int (*write_to_remote)(apacket* p, atransport* t) = nullptr; - void (*close)(atransport* t) = nullptr; - void (*kick)(atransport* t) = nullptr; - - int fd = -1; - int transport_socket = -1; - fdevent transport_fde; - int ref_count = 0; - uint32_t sync_token = 0; - ConnectionState connection_state = kCsOffline; - bool online = false; - TransportType type = kTransportAny; - - // USB handle or socket fd as needed. - usb_handle* usb = nullptr; - int sfd = -1; - - // Used to identify transports for clients. - char* serial = nullptr; - char* product = nullptr; - char* model = nullptr; - char* device = nullptr; - char* devpath = nullptr; - int adb_port = -1; // Use for emulators (local transport) - bool kicked = false; - - // A list of adisconnect callbacks called when the transport is kicked. - adisconnect disconnects = {}; - - void* key = nullptr; - unsigned char token[TOKEN_SIZE] = {}; - fdevent auth_fde; - size_t failed_auth_attempts = 0; - - const char* connection_state_name() const; - - void update_version(int version, size_t payload); - int get_protocol_version() const; - size_t get_max_payload() const; - -private: - int protocol_version; - size_t max_payload; - - DISALLOW_COPY_AND_ASSIGN(atransport); -}; - - /* A listener is an entity which binds to a local port ** and, upon receiving a connection on that port, creates ** an asocket to connect the new local connection to a @@ -380,7 +315,8 @@ int adb_commandline(int argc, const char **argv); ConnectionState connection_state(atransport *t); -extern const char *adb_device_banner; +extern const char* adb_device_banner; + #if !ADB_HOST extern int SHELL_EXIT_NOTIFY_FD; #endif // !ADB_HOST @@ -405,4 +341,6 @@ void handle_offline(atransport *t); void send_connect(atransport *t); +void parse_banner(const std::string&, atransport* t); + #endif diff --git a/adb/transport.cpp b/adb/transport.cpp index 4a273c482..2ea4d4469 100644 --- a/adb/transport.cpp +++ b/adb/transport.cpp @@ -29,6 +29,7 @@ #include #include +#include #include "adb.h" #include "adb_utils.h" @@ -535,7 +536,7 @@ static void transport_registration_func(int _fd, unsigned ev, void *data) t = m.transport; - if(m.action == 0){ + if (m.action == 0) { D("transport: %s removing and free'ing %d\n", t->serial, t->transport_socket); /* IMPORTANT: the remove closes one half of the @@ -845,6 +846,29 @@ size_t atransport::get_max_payload() const { return max_payload; } +// The list of features supported by the current system. Will be sent to the +// other side of the connection in the banner. +static const FeatureSet gSupportedFeatures = { + // None yet. +}; + +const FeatureSet& supported_features() { + return gSupportedFeatures; +} + +bool atransport::has_feature(const std::string& feature) const { + return features_.count(feature) > 0; +} + +void atransport::add_feature(const std::string& feature) { + features_.insert(feature); +} + +bool atransport::CanUseFeature(const std::string& feature) const { + return has_feature(feature) && + supported_features().count(feature) > 0; +} + #if ADB_HOST static void append_transport_info(std::string* result, const char* key, @@ -879,6 +903,9 @@ static void append_transport(const atransport* t, std::string* result, append_transport_info(result, "product:", t->product, false); append_transport_info(result, "model:", t->model, true); append_transport_info(result, "device:", t->device, false); + append_transport_info(result, "features:", + android::base::Join(t->features(), ',').c_str(), + false); } *result += '\n'; } diff --git a/adb/transport.h b/adb/transport.h index edcc99ded..e809407c5 100644 --- a/adb/transport.h +++ b/adb/transport.h @@ -20,9 +20,92 @@ #include #include +#include #include "adb.h" +typedef std::unordered_set FeatureSet; + +const FeatureSet& supported_features(); + +class atransport { +public: + // TODO(danalbert): We expose waaaaaaay too much stuff because this was + // historically just a struct, but making the whole thing a more idiomatic + // class in one go is a very large change. Given how bad our testing is, + // it's better to do this piece by piece. + + atransport() { + auth_fde = {}; + transport_fde = {}; + protocol_version = A_VERSION; + max_payload = MAX_PAYLOAD; + } + + virtual ~atransport() {} + + int (*read_from_remote)(apacket* p, atransport* t) = nullptr; + int (*write_to_remote)(apacket* p, atransport* t) = nullptr; + void (*close)(atransport* t) = nullptr; + void (*kick)(atransport* t) = nullptr; + + int fd = -1; + int transport_socket = -1; + fdevent transport_fde; + int ref_count = 0; + uint32_t sync_token = 0; + ConnectionState connection_state = kCsOffline; + bool online = false; + TransportType type = kTransportAny; + + // USB handle or socket fd as needed. + usb_handle* usb = nullptr; + int sfd = -1; + + // Used to identify transports for clients. + char* serial = nullptr; + char* product = nullptr; + char* model = nullptr; + char* device = nullptr; + char* devpath = nullptr; + int adb_port = -1; // Use for emulators (local transport) + bool kicked = false; + + // A list of adisconnect callbacks called when the transport is kicked. + adisconnect disconnects = {}; + + void* key = nullptr; + unsigned char token[TOKEN_SIZE] = {}; + fdevent auth_fde; + size_t failed_auth_attempts = 0; + + const char* connection_state_name() const; + + void update_version(int version, size_t payload); + int get_protocol_version() const; + size_t get_max_payload() const; + + inline const FeatureSet features() const { + return features_; + } + + bool has_feature(const std::string& feature) const; + void add_feature(const std::string& feature); + + // Returns true if both we and the other end of the transport support the + // feature. + bool CanUseFeature(const std::string& feature) const; + +private: + // A set of features transmitted in the banner with the initial connection. + // This is stored in the banner as 'features=feature0,feature1,etc'. + FeatureSet features_; + int protocol_version; + size_t max_payload; + + DISALLOW_COPY_AND_ASSIGN(atransport); +}; + /* * Obtain a transport from the available transports. * If state is != kCsAny, only transports in that state are considered. diff --git a/adb/transport_test.cpp b/adb/transport_test.cpp index 49deb73d2..743d97d32 100644 --- a/adb/transport_test.cpp +++ b/adb/transport_test.cpp @@ -59,6 +59,8 @@ public: EXPECT_EQ(0, memcmp(&auth_fde, &rhs.auth_fde, sizeof(fdevent))); EXPECT_EQ(failed_auth_attempts, rhs.failed_auth_attempts); + EXPECT_EQ(features(), rhs.features()); + return true; } }; @@ -120,6 +122,73 @@ TEST(transport, kick_transport_already_kicked) { // want to make sure I understand how this is working at all before I try fixing // that. TEST(transport, DISABLED_run_transport_disconnects_zeroed_atransport) { - atransport t; - run_transport_disconnects(&t); + atransport t; + run_transport_disconnects(&t); +} + +TEST(transport, add_feature) { + atransport t; + ASSERT_EQ(0U, t.features().size()); + + t.add_feature("foo"); + ASSERT_EQ(1U, t.features().size()); + ASSERT_TRUE(t.has_feature("foo")); + + t.add_feature("bar"); + ASSERT_EQ(2U, t.features().size()); + ASSERT_TRUE(t.has_feature("foo")); + ASSERT_TRUE(t.has_feature("bar")); + + t.add_feature("foo"); + ASSERT_EQ(2U, t.features().size()); + ASSERT_TRUE(t.has_feature("foo")); + ASSERT_TRUE(t.has_feature("bar")); +} + +TEST(transport, parse_banner_no_features) { + atransport t; + + parse_banner("host::", &t); + + ASSERT_EQ(0U, t.features().size()); + ASSERT_EQ(kCsHost, t.connection_state); + + ASSERT_EQ(nullptr, t.product); + ASSERT_EQ(nullptr, t.model); + ASSERT_EQ(nullptr, t.device); +} + +TEST(transport, parse_banner_product_features) { + atransport t; + + const char banner[] = + "host::ro.product.name=foo;ro.product.model=bar;ro.product.device=baz;"; + parse_banner(banner, &t); + + ASSERT_EQ(kCsHost, t.connection_state); + + ASSERT_EQ(0U, t.features().size()); + + ASSERT_EQ(std::string("foo"), t.product); + ASSERT_EQ(std::string("bar"), t.model); + ASSERT_EQ(std::string("baz"), t.device); +} + +TEST(transport, parse_banner_features) { + atransport t; + + const char banner[] = + "host::ro.product.name=foo;ro.product.model=bar;ro.product.device=baz;" + "features=woodly,doodly"; + parse_banner(banner, &t); + + ASSERT_EQ(kCsHost, t.connection_state); + + ASSERT_EQ(2U, t.features().size()); + ASSERT_TRUE(t.has_feature("woodly")); + ASSERT_TRUE(t.has_feature("doodly")); + + ASSERT_EQ(std::string("foo"), t.product); + ASSERT_EQ(std::string("bar"), t.model); + ASSERT_EQ(std::string("baz"), t.device); }