Merge changes from topic "adb-mdns"

* changes:
  adb pair: allow passing password as command-line argument.
  Change adb auto-connect to use mdns instance name.
  'adb pair' by mdns instance name.
  'adb connect' by mDNS service name.
  Add mDNS service instance name parser.
This commit is contained in:
Joshua Duong 2020-05-19 00:30:06 +00:00 committed by Gerrit Code Review
commit 7ce4a267f5
10 changed files with 549 additions and 59 deletions

View file

@ -218,6 +218,7 @@ cc_library_host_static {
"client/usb_dispatch.cpp",
"client/transport_local.cpp",
"client/transport_mdns.cpp",
"client/mdns_utils.cpp",
"client/transport_usb.cpp",
"client/pairing/pairing_client.cpp",
],
@ -264,7 +265,10 @@ cc_library_host_static {
cc_test_host {
name: "adb_test",
defaults: ["adb_defaults"],
srcs: libadb_test_srcs,
srcs: libadb_test_srcs + [
"client/mdns_utils_test.cpp",
],
static_libs: [
"libadb_crypto_static",
"libadb_host",

View file

@ -16,6 +16,7 @@
#pragma once
#include <optional>
#include <string>
#include "adb.h"
@ -30,6 +31,19 @@ bool adb_wifi_is_known_host(const std::string& host);
std::string mdns_check();
std::string mdns_list_discovered_services();
struct MdnsInfo {
std::string service_name;
std::string service_type;
std::string addr;
uint16_t port = 0;
MdnsInfo(std::string_view name, std::string_view type, std::string_view addr, uint16_t port)
: service_name(name), service_type(type), addr(addr), port(port) {}
};
std::optional<MdnsInfo> mdns_get_connect_service_info(std::string_view name);
std::optional<MdnsInfo> mdns_get_pairing_service_info(std::string_view name);
#else // !ADB_HOST
struct AdbdAuthContext;

View file

@ -179,17 +179,21 @@ bool adb_wifi_is_known_host(const std::string& host) {
void adb_wifi_pair_device(const std::string& host, const std::string& password,
std::string& response) {
// Check the address for a valid address and port.
std::string parsed_host;
std::string err;
int port = -1;
if (!android::base::ParseNetAddress(host, &parsed_host, &port, nullptr, &err)) {
response = "Failed to parse address for pairing: " + err;
return;
}
if (port <= 0 || port > 65535) {
response = "Invalid port while parsing address [" + host + "]";
return;
auto mdns_info = mdns_get_pairing_service_info(host);
if (!mdns_info.has_value()) {
// Check the address for a valid address and port.
std::string parsed_host;
std::string err;
int port = -1;
if (!android::base::ParseNetAddress(host, &parsed_host, &port, nullptr, &err)) {
response = "Failed to parse address for pairing: " + err;
return;
}
if (port <= 0 || port > 65535) {
response = "Invalid port while parsing address [" + host + "]";
return;
}
}
auto priv_key = adb_auth_get_user_privkey();
@ -220,7 +224,11 @@ void adb_wifi_pair_device(const std::string& host, const std::string& password,
PairingResultWaiter waiter;
std::unique_lock<std::mutex> lock(waiter.mutex_);
if (!client->Start(host, waiter.OnResult, &waiter)) {
if (!client->Start(mdns_info.has_value()
? android::base::StringPrintf("%s:%d", mdns_info->addr.c_str(),
mdns_info->port)
: host,
waiter.OnResult, &waiter)) {
response = "Failed: Unable to start pairing client.";
return;
}

View file

@ -104,7 +104,8 @@ static void help() {
" connect HOST[:PORT] connect to a device via TCP/IP [default port=5555]\n"
" disconnect [HOST[:PORT]]\n"
" disconnect from given TCP/IP device [default port=5555], or all\n"
" pair HOST[:PORT] pair with a device for secure TCP/IP communication\n"
" pair HOST[:PORT] [PAIRING CODE]\n"
" pair with a device for secure TCP/IP communication\n"
" forward --list list all forward socket connections\n"
" forward [--no-rebind] LOCAL REMOTE\n"
" forward socket connection using:\n"
@ -1735,13 +1736,17 @@ int adb_commandline(int argc, const char** argv) {
} else if (!strcmp(argv[0], "abb")) {
return adb_abb(argc, argv);
} else if (!strcmp(argv[0], "pair")) {
if (argc != 2) error_exit("usage: adb pair <host>[:<port>]");
if (argc < 2 || argc > 3) error_exit("usage: adb pair HOST[:PORT] [PAIRING CODE]");
std::string password;
printf("Enter pairing code: ");
fflush(stdout);
if (!std::getline(std::cin, password) || password.empty()) {
error_exit("No pairing code provided");
if (argc == 2) {
printf("Enter pairing code: ");
fflush(stdout);
if (!std::getline(std::cin, password) || password.empty()) {
error_exit("No pairing code provided");
}
} else {
password = argv[2];
}
std::string query =
android::base::StringPrintf("host:pair:%s:%s", password.c_str(), argv[1]);

77
adb/client/mdns_utils.cpp Normal file
View file

@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "client/mdns_utils.h"
#include <android-base/strings.h>
namespace mdns {
// <Instance>.<Service>.<Domain>
std::optional<MdnsInstance> mdns_parse_instance_name(std::string_view name) {
CHECK(!name.empty());
// Return the whole name if it doesn't fall under <Instance>.<Service>.<Domain> or
// <Instance>.<Service>
bool has_local_suffix = false;
// Strip the local suffix, if any
{
std::string local_suffix = ".local";
local_suffix += android::base::EndsWith(name, ".") ? "." : "";
if (android::base::ConsumeSuffix(&name, local_suffix)) {
if (name.empty()) {
return std::nullopt;
}
has_local_suffix = true;
}
}
std::string transport;
// Strip the transport suffix, if any
{
std::string add_dot = (!has_local_suffix && android::base::EndsWith(name, ".")) ? "." : "";
std::array<std::string, 2> transport_suffixes{"._tcp", "._udp"};
for (const auto& t : transport_suffixes) {
if (android::base::ConsumeSuffix(&name, t + add_dot)) {
if (name.empty()) {
return std::nullopt;
}
transport = t.substr(1);
break;
}
}
if (has_local_suffix && transport.empty()) {
return std::nullopt;
}
}
if (!has_local_suffix && transport.empty()) {
return std::make_optional<MdnsInstance>(name, "", "");
}
// Split the service name from the instance name
auto pos = name.rfind(".");
if (pos == 0 || pos == std::string::npos || pos == name.size() - 1) {
return std::nullopt;
}
return std::make_optional<MdnsInstance>(name.substr(0, pos), name.substr(pos + 1), transport);
}
} // namespace mdns

54
adb/client/mdns_utils.h Normal file
View file

@ -0,0 +1,54 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <optional>
#include <string_view>
#include "adb_wifi.h"
namespace mdns {
struct MdnsInstance {
std::string instance_name; // "my name"
std::string service_name; // "_adb-tls-connect"
std::string transport_type; // either "_tcp" or "_udp"
MdnsInstance(std::string_view inst, std::string_view serv, std::string_view trans)
: instance_name(inst), service_name(serv), transport_type(trans) {}
};
// This parser is based on https://tools.ietf.org/html/rfc6763#section-4.1 for
// structured service instance names, where the whole name is in the format
// <Instance>.<Service>.<Domain>.
//
// In our case, we ignore <Domain> portion of the name, which
// we always assume to be ".local", or link-local mDNS.
//
// The string can be in one of the following forms:
// - <Instance>.<Service>.<Domain>.?
// - e.g. "instance._service._tcp.local" (or "...local.")
// - <Instance>.<Service>.? (must contain either "_tcp" or "_udp" at the end)
// - e.g. "instance._service._tcp" (or "..._tcp.)
// - <Instance> (can contain dots '.')
// - e.g. "myname", "name.", "my.name."
//
// Returns an MdnsInstance with the appropriate fields filled in (instance name is never empty),
// otherwise returns std::nullopt.
std::optional<MdnsInstance> mdns_parse_instance_name(std::string_view name);
} // namespace mdns

View file

@ -0,0 +1,173 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "client/mdns_utils.h"
#include <gtest/gtest.h>
namespace mdns {
TEST(mdns_utils, mdns_parse_instance_name) {
// Just the instance name
{
std::string str = ".";
auto res = mdns_parse_instance_name(str);
ASSERT_TRUE(res.has_value());
EXPECT_EQ(str, res->instance_name);
EXPECT_TRUE(res->service_name.empty());
EXPECT_TRUE(res->transport_type.empty());
}
{
std::string str = "my.name";
auto res = mdns_parse_instance_name(str);
ASSERT_TRUE(res.has_value());
EXPECT_EQ(str, res->instance_name);
EXPECT_TRUE(res->service_name.empty());
EXPECT_TRUE(res->transport_type.empty());
}
{
std::string str = "my.name.";
auto res = mdns_parse_instance_name(str);
ASSERT_TRUE(res.has_value());
EXPECT_EQ(str, res->instance_name);
EXPECT_TRUE(res->service_name.empty());
EXPECT_TRUE(res->transport_type.empty());
}
// With "_tcp", "_udp" transport type
for (const std::string_view transport : {"._tcp", "._udp"}) {
{
std::string str = android::base::StringPrintf("%s", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = android::base::StringPrintf("%s.", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = android::base::StringPrintf("service%s", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = android::base::StringPrintf(".service%s", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = android::base::StringPrintf("service.%s", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = android::base::StringPrintf("my.service%s", transport.data());
auto res = mdns_parse_instance_name(str);
ASSERT_TRUE(res.has_value());
EXPECT_EQ(res->instance_name, "my");
EXPECT_EQ(res->service_name, "service");
EXPECT_EQ(res->transport_type, transport.substr(1));
}
{
std::string str = android::base::StringPrintf("my.service%s.", transport.data());
auto res = mdns_parse_instance_name(str);
ASSERT_TRUE(res.has_value());
EXPECT_EQ(res->instance_name, "my");
EXPECT_EQ(res->service_name, "service");
EXPECT_EQ(res->transport_type, transport.substr(1));
}
{
std::string str = android::base::StringPrintf("my..service%s", transport.data());
auto res = mdns_parse_instance_name(str);
ASSERT_TRUE(res.has_value());
EXPECT_EQ(res->instance_name, "my.");
EXPECT_EQ(res->service_name, "service");
EXPECT_EQ(res->transport_type, transport.substr(1));
}
{
std::string str = android::base::StringPrintf("my.name.service%s.", transport.data());
auto res = mdns_parse_instance_name(str);
ASSERT_TRUE(res.has_value());
EXPECT_EQ(res->instance_name, "my.name");
EXPECT_EQ(res->service_name, "service");
EXPECT_EQ(res->transport_type, transport.substr(1));
}
{
std::string str = android::base::StringPrintf("name.service.%s.", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
// With ".local" domain
{
std::string str = ".local";
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = ".local.";
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = "name.local";
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = android::base::StringPrintf("%s.local", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = android::base::StringPrintf("service%s.local", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str = android::base::StringPrintf("name.service%s.local", transport.data());
auto res = mdns_parse_instance_name(str);
ASSERT_TRUE(res.has_value());
EXPECT_EQ(res->instance_name, "name");
EXPECT_EQ(res->service_name, "service");
EXPECT_EQ(res->transport_type, transport.substr(1));
}
{
std::string str =
android::base::StringPrintf("name.service%s.local.", transport.data());
auto res = mdns_parse_instance_name(str);
ASSERT_TRUE(res.has_value());
EXPECT_EQ(res->instance_name, "name");
EXPECT_EQ(res->service_name, "service");
EXPECT_EQ(res->transport_type, transport.substr(1));
}
{
std::string str =
android::base::StringPrintf("name.service%s..local.", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
{
std::string str =
android::base::StringPrintf("name.service.%s.local.", transport.data());
auto res = mdns_parse_instance_name(str);
EXPECT_FALSE(res.has_value());
}
}
}
} // namespace mdns

View file

@ -38,6 +38,7 @@
#include "adb_trace.h"
#include "adb_utils.h"
#include "adb_wifi.h"
#include "client/mdns_utils.h"
#include "fdevent/fdevent.h"
#include "sysdeps.h"
@ -221,7 +222,7 @@ class ResolvedService : public AsyncServiceRef {
}
std::string response;
connect_device(android::base::StringPrintf(addr_format_.c_str(), ip_addr_, port_),
connect_device(android::base::StringPrintf("%s.%s", serviceName_.c_str(), regType_.c_str()),
&response);
D("Secure connect to %s regtype %s (%s:%hu) : %s", serviceName_.c_str(), regType_.c_str(),
ip_addr_, port_, response.c_str());
@ -248,26 +249,8 @@ class ResolvedService : public AsyncServiceRef {
return false;
}
// adb secure service needs to do something different from just
// connecting here.
if (adb_DNSServiceShouldAutoConnect(regType_.c_str(), serviceName_.c_str())) {
std::string response;
D("Attempting to serviceName=[%s], regtype=[%s] ipaddr=(%s:%hu)", serviceName_.c_str(),
regType_.c_str(), ip_addr_, port_);
int index = adb_DNSServiceIndexByName(regType_.c_str());
if (index == kADBSecureConnectServiceRefIndex) {
ConnectSecureWifiDevice();
} else {
connect_device(android::base::StringPrintf(addr_format_.c_str(), ip_addr_, port_),
&response);
D("Connect to %s regtype %s (%s:%hu) : %s", serviceName_.c_str(), regType_.c_str(),
ip_addr_, port_, response.c_str());
}
} else {
D("Not immediately connecting to serviceName=[%s], regtype=[%s] ipaddr=(%s:%hu)",
serviceName_.c_str(), regType_.c_str(), ip_addr_, port_);
}
// Add to the service registry before trying to auto-connect, since socket_spec_connect will
// check these registries for the ip address when connecting via mdns instance name.
int adbSecureServiceType = serviceIndex();
ServiceRegistry* services = nullptr;
switch (adbSecureServiceType) {
@ -294,6 +277,25 @@ class ResolvedService : public AsyncServiceRef {
}
services->push_back(std::unique_ptr<ResolvedService>(this));
if (adb_DNSServiceShouldAutoConnect(regType_.c_str(), serviceName_.c_str())) {
std::string response;
D("Attempting to connect serviceName=[%s], regtype=[%s] ipaddr=(%s:%hu)",
serviceName_.c_str(), regType_.c_str(), ip_addr_, port_);
int index = adb_DNSServiceIndexByName(regType_.c_str());
if (index == kADBSecureConnectServiceRefIndex) {
ConnectSecureWifiDevice();
} else {
connect_device(android::base::StringPrintf("%s.%s", serviceName_.c_str(),
regType_.c_str()),
&response);
D("Connect to %s regtype %s (%s:%hu) : %s", serviceName_.c_str(), regType_.c_str(),
ip_addr_, port_, response.c_str());
}
} else {
D("Not immediately connecting to serviceName=[%s], regtype=[%s] ipaddr=(%s:%hu)",
serviceName_.c_str(), regType_.c_str(), ip_addr_, port_);
}
return true;
}
@ -319,7 +321,7 @@ class ResolvedService : public AsyncServiceRef {
static void initAdbServiceRegistries();
static void forEachService(const ServiceRegistry& services, const std::string& hostname,
static void forEachService(const ServiceRegistry& services, std::string_view hostname,
adb_secure_foreach_service_callback cb);
static bool connectByServiceName(const ServiceRegistry& services,
@ -362,7 +364,7 @@ void ResolvedService::initAdbServiceRegistries() {
// static
void ResolvedService::forEachService(const ServiceRegistry& services,
const std::string& wanted_service_name,
std::string_view wanted_service_name,
adb_secure_foreach_service_callback cb) {
initAdbServiceRegistries();
@ -372,7 +374,7 @@ void ResolvedService::forEachService(const ServiceRegistry& services,
auto ip = service->ipAddress();
auto port = service->port();
if (wanted_service_name == "") {
if (wanted_service_name.empty()) {
cb(service_name.c_str(), reg_type.c_str(), ip.c_str(), port);
} else if (service_name == wanted_service_name) {
cb(service_name.c_str(), reg_type.c_str(), ip.c_str(), port);
@ -396,14 +398,12 @@ bool ResolvedService::connectByServiceName(const ServiceRegistry& services,
void adb_secure_foreach_pairing_service(const char* service_name,
adb_secure_foreach_service_callback cb) {
ResolvedService::forEachService(*ResolvedService::sAdbSecurePairingServices,
service_name ? service_name : "", cb);
ResolvedService::forEachService(*ResolvedService::sAdbSecurePairingServices, service_name, cb);
}
void adb_secure_foreach_connect_service(const char* service_name,
adb_secure_foreach_service_callback cb) {
ResolvedService::forEachService(*ResolvedService::sAdbSecureConnectServices,
service_name ? service_name : "", cb);
ResolvedService::forEachService(*ResolvedService::sAdbSecureConnectServices, service_name, cb);
}
bool adb_secure_connect_by_service_name(const char* service_name) {
@ -676,3 +676,79 @@ std::string mdns_list_discovered_services() {
ResolvedService::forEachService(*ResolvedService::sAdbSecurePairingServices, "", cb);
return result;
}
std::optional<MdnsInfo> mdns_get_connect_service_info(std::string_view name) {
CHECK(!name.empty());
auto mdns_instance = mdns::mdns_parse_instance_name(name);
if (!mdns_instance.has_value()) {
D("Failed to parse mDNS name [%s]", name.data());
return std::nullopt;
}
std::optional<MdnsInfo> info;
auto cb = [&](const char* service_name, const char* reg_type, const char* ip_addr,
uint16_t port) { info.emplace(service_name, reg_type, ip_addr, port); };
std::string reg_type;
if (!mdns_instance->service_name.empty()) {
reg_type = android::base::StringPrintf("%s.%s", mdns_instance->service_name.data(),
mdns_instance->transport_type.data());
int index = adb_DNSServiceIndexByName(reg_type);
switch (index) {
case kADBTransportServiceRefIndex:
ResolvedService::forEachService(*ResolvedService::sAdbTransportServices,
mdns_instance->instance_name, cb);
break;
case kADBSecureConnectServiceRefIndex:
ResolvedService::forEachService(*ResolvedService::sAdbSecureConnectServices,
mdns_instance->instance_name, cb);
break;
default:
D("Unknown reg_type [%s]", reg_type.data());
return std::nullopt;
}
return info;
}
for (const auto& service :
{ResolvedService::sAdbTransportServices, ResolvedService::sAdbSecureConnectServices}) {
ResolvedService::forEachService(*service, name, cb);
if (info.has_value()) {
return info;
}
}
return std::nullopt;
}
std::optional<MdnsInfo> mdns_get_pairing_service_info(std::string_view name) {
CHECK(!name.empty());
auto mdns_instance = mdns::mdns_parse_instance_name(name);
if (!mdns_instance.has_value()) {
D("Failed to parse mDNS pairing name [%s]", name.data());
return std::nullopt;
}
std::optional<MdnsInfo> info;
auto cb = [&](const char* service_name, const char* reg_type, const char* ip_addr,
uint16_t port) { info.emplace(service_name, reg_type, ip_addr, port); };
// Verify it's a pairing service if user explicitly inputs it.
if (!mdns_instance->service_name.empty()) {
auto reg_type = android::base::StringPrintf("%s.%s", mdns_instance->service_name.data(),
mdns_instance->transport_type.data());
int index = adb_DNSServiceIndexByName(reg_type);
switch (index) {
case kADBSecurePairingServiceRefIndex:
break;
default:
D("Not an adb pairing reg_type [%s]", reg_type.data());
return std::nullopt;
}
}
ResolvedService::forEachService(*ResolvedService::sAdbSecurePairingServices, name, cb);
return info;
}

View file

@ -30,6 +30,7 @@
#include "adb.h"
#include "adb_utils.h"
#include "adb_wifi.h"
#include "sysdeps.h"
using namespace std::string_literals;
@ -195,7 +196,24 @@ bool socket_spec_connect(unique_fd* fd, std::string_view address, int* port, std
fd->reset(network_loopback_client(port_value, SOCK_STREAM, error));
} else {
#if ADB_HOST
fd->reset(network_connect(hostname, port_value, SOCK_STREAM, 0, error));
// Check if the address is an mdns service we can connect to.
if (auto mdns_info = mdns_get_connect_service_info(address.substr(4));
mdns_info != std::nullopt) {
fd->reset(network_connect(mdns_info->addr, mdns_info->port, SOCK_STREAM, 0, error));
if (fd->get() != -1) {
// TODO(joshuaduong): We still show the ip address for the serial. Change it to
// use the mdns instance name, so we can adjust to address changes on
// reconnects.
port_value = mdns_info->port;
if (serial) {
*serial = android::base::StringPrintf("%s.%s",
mdns_info->service_name.c_str(),
mdns_info->service_type.c_str());
}
}
} else {
fd->reset(network_connect(hostname, port_value, SOCK_STREAM, 0, error));
}
#else
// Disallow arbitrary connections in adbd.
*error = "adbd does not support arbitrary tcp connections";

View file

@ -25,6 +25,7 @@ import os
import random
import select
import socket
import string
import struct
import subprocess
import sys
@ -628,21 +629,49 @@ def zeroconf_register_service(zeroconf_ctx, info):
class MdnsTest(unittest.TestCase):
"""Tests for adb mdns."""
@staticmethod
def _mdns_services(port):
output = subprocess.check_output(["adb", "-P", str(port), "mdns", "services"])
return [x.split("\t") for x in output.decode("utf8").strip().splitlines()[1:]]
@staticmethod
def _devices(port):
output = subprocess.check_output(["adb", "-P", str(port), "devices"])
return [x.split("\t") for x in output.decode("utf8").strip().splitlines()[1:]]
@contextlib.contextmanager
def _adb_mdns_connect(self, server_port, mdns_instance, serial, should_connect):
"""Context manager for an ADB connection.
This automatically disconnects when done with the connection.
"""
output = subprocess.check_output(["adb", "-P", str(server_port), "connect", mdns_instance])
if should_connect:
self.assertEqual(output.strip(), "connected to {}".format(serial).encode("utf8"))
else:
self.assertTrue(output.startswith("failed to resolve host: '{}'"
.format(mdns_instance).encode("utf8")))
try:
yield
finally:
# Perform best-effort disconnection. Discard the output.
subprocess.Popen(["adb", "disconnect", serial],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
@unittest.skipIf(not is_zeroconf_installed(), "zeroconf library not installed")
def test_mdns_services_register_unregister(self):
"""Ensure that `adb mdns services` correctly adds and removes a service
"""
from zeroconf import IPVersion, ServiceInfo
def _mdns_services(port):
output = subprocess.check_output(["adb", "-P", str(port), "mdns", "services"])
return [x.split("\t") for x in output.decode("utf8").strip().splitlines()[1:]]
with adb_server() as server_port:
output = subprocess.check_output(["adb", "-P", str(server_port),
"mdns", "services"]).strip()
self.assertTrue(output.startswith(b"List of discovered mdns services"))
print(f"services={_mdns_services(server_port)}")
"""TODO(joshuaduong): Add ipv6 tests once we have it working in adb"""
"""Register/Unregister a service"""
@ -656,20 +685,52 @@ class MdnsTest(unittest.TestCase):
name=serv_instance + "." + serv_type + "local.",
addresses=[serv_ipaddr],
port=serv_port)
print(f"Registering {serv_instance}.{serv_type} ...")
with zeroconf_register_service(zc, service_info) as info:
"""Give adb some time to register the service"""
time.sleep(1)
print(f"services={_mdns_services(server_port)}")
self.assertTrue(any((serv_instance in line and serv_type in line)
for line in _mdns_services(server_port)))
for line in MdnsTest._mdns_services(server_port)))
"""Give adb some time to unregister the service"""
print("Unregistering mdns service...")
time.sleep(1)
print(f"services={_mdns_services(server_port)}")
self.assertFalse(any((serv_instance in line and serv_type in line)
for line in _mdns_services(server_port)))
for line in MdnsTest._mdns_services(server_port)))
@unittest.skipIf(not is_zeroconf_installed(), "zeroconf library not installed")
def test_mdns_connect(self):
"""Ensure that `adb connect` by mdns instance name works (for non-pairing services)
"""
from zeroconf import IPVersion, ServiceInfo
with adb_server() as server_port:
with zeroconf_context(IPVersion.V4Only) as zc:
serv_instance = "fakeadbd-" + ''.join(
random.choice(string.ascii_letters) for i in range(4))
serv_type = "_" + self.service_name + "._tcp."
serv_ipaddr = socket.inet_aton("127.0.0.1")
should_connect = self.service_name != "adb-tls-pairing"
with fake_adbd() as (port, _):
service_info = ServiceInfo(
serv_type + "local.",
name=serv_instance + "." + serv_type + "local.",
addresses=[serv_ipaddr],
port=port)
with zeroconf_register_service(zc, service_info) as info:
"""Give adb some time to register the service"""
time.sleep(1)
self.assertTrue(any((serv_instance in line and serv_type in line)
for line in MdnsTest._mdns_services(server_port)))
full_name = '.'.join([serv_instance, serv_type])
with self._adb_mdns_connect(server_port, serv_instance, full_name,
should_connect):
if should_connect:
self.assertEqual(MdnsTest._devices(server_port),
[[full_name, "device"]])
"""Give adb some time to unregister the service"""
time.sleep(1)
self.assertFalse(any((serv_instance in line and serv_type in line)
for line in MdnsTest._mdns_services(server_port)))
def main():
"""Main entrypoint."""