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
diff --git a/Android.bp b/Android.bp
index 432770c..9db151d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -218,6 +218,7 @@
"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_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",
diff --git a/client/mdns_utils.cpp b/client/mdns_utils.cpp
new file mode 100644
index 0000000..8666b18
--- /dev/null
+++ b/client/mdns_utils.cpp
@@ -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
diff --git a/client/mdns_utils.h b/client/mdns_utils.h
new file mode 100644
index 0000000..40d095d
--- /dev/null
+++ b/client/mdns_utils.h
@@ -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
diff --git a/client/mdns_utils_test.cpp b/client/mdns_utils_test.cpp
new file mode 100644
index 0000000..ec71529
--- /dev/null
+++ b/client/mdns_utils_test.cpp
@@ -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