Add mDNS service instance name parser.

This will be used for parsing user-provided names to 'adb connect' and
'adb pair' in order to check for matches in the mdns service registry.

Bug: 152886765

Test: $ANDROID_HOST_OUT/nativetest64/adb_test/adb_test
--gtest_filter=mdns_utils*

Change-Id: Ifd74b4394212853c1c193a2ea64937f6a6a0ff24
This commit is contained in:
Joshua Duong 2020-04-30 15:45:38 -07:00
parent 972f1ba172
commit 7be8519cdb
4 changed files with 309 additions and 1 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",

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