Loading adb/adb.cpp +64 −47 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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 std::string get_connection_string() { std::vector<std::string> 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(); 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); } Loading @@ -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;". Loading @@ -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); } } } } Loading Loading @@ -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<const char*>(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; Loading @@ -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<const char*>(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: Loading adb/adb.h +6 −68 Original line number Diff line number Diff line Loading @@ -20,10 +20,10 @@ #include <limits.h> #include <sys/types.h> #include <base/macros.h> #include <string> #include <base/macros.h> #include "adb_trace.h" #include "fdevent.h" Loading Loading @@ -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 Loading Loading @@ -381,6 +316,7 @@ int adb_commandline(int argc, const char **argv); ConnectionState connection_state(atransport *t); extern const char* adb_device_banner; #if !ADB_HOST extern int SHELL_EXIT_NOTIFY_FD; #endif // !ADB_HOST Loading @@ -405,4 +341,6 @@ void handle_offline(atransport *t); void send_connect(atransport *t); void parse_banner(const std::string&, atransport* t); #endif adb/transport.cpp +28 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ #include <list> #include <base/stringprintf.h> #include <base/strings.h> #include "adb.h" #include "adb_utils.h" Loading Loading @@ -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, Loading Loading @@ -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'; } Loading adb/transport.h +83 −0 Original line number Diff line number Diff line Loading @@ -20,9 +20,92 @@ #include <sys/types.h> #include <string> #include <unordered_set> #include "adb.h" typedef std::unordered_set<std::string> 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. Loading adb/transport_test.cpp +71 −2 Original line number Diff line number Diff line Loading @@ -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; } }; Loading Loading @@ -123,3 +125,70 @@ TEST(transport, DISABLED_run_transport_disconnects_zeroed_atransport) { 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); } Loading
adb/adb.cpp +64 −47 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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 std::string get_connection_string() { std::vector<std::string> 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(); 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); } Loading @@ -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;". Loading @@ -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); } } } } Loading Loading @@ -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<const char*>(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; Loading @@ -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<const char*>(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: Loading
adb/adb.h +6 −68 Original line number Diff line number Diff line Loading @@ -20,10 +20,10 @@ #include <limits.h> #include <sys/types.h> #include <base/macros.h> #include <string> #include <base/macros.h> #include "adb_trace.h" #include "fdevent.h" Loading Loading @@ -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 Loading Loading @@ -381,6 +316,7 @@ int adb_commandline(int argc, const char **argv); ConnectionState connection_state(atransport *t); extern const char* adb_device_banner; #if !ADB_HOST extern int SHELL_EXIT_NOTIFY_FD; #endif // !ADB_HOST Loading @@ -405,4 +341,6 @@ void handle_offline(atransport *t); void send_connect(atransport *t); void parse_banner(const std::string&, atransport* t); #endif
adb/transport.cpp +28 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ #include <list> #include <base/stringprintf.h> #include <base/strings.h> #include "adb.h" #include "adb_utils.h" Loading Loading @@ -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, Loading Loading @@ -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'; } Loading
adb/transport.h +83 −0 Original line number Diff line number Diff line Loading @@ -20,9 +20,92 @@ #include <sys/types.h> #include <string> #include <unordered_set> #include "adb.h" typedef std::unordered_set<std::string> 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. Loading
adb/transport_test.cpp +71 −2 Original line number Diff line number Diff line Loading @@ -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; } }; Loading Loading @@ -123,3 +125,70 @@ TEST(transport, DISABLED_run_transport_disconnects_zeroed_atransport) { 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); }