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