Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7be8519c authored by Joshua Duong's avatar Joshua Duong
Browse files

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
parent 972f1ba1
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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 −0
Original line number Diff line number Diff line
/*
 * 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
+173 −0
Original line number Diff line number Diff line
/*
 * 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