// Copyright 2021 gRPC authors.
//
// 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 <gtest/gtest.h>

#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/iomgr/port.h"
#include "test/core/util/test_config.h"

#ifdef GRPC_HAVE_UNIX_SOCKET

#include <sys/un.h>

#include <cstring>

#include <grpc/grpc.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>

#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/resolver/resolver_registry.h"

// Registers the factory with `grpc_core::ResolverRegistry`. Defined in
// binder_resolver.cc
namespace grpc_core {
void RegisterBinderResolver(CoreConfiguration::Builder* builder);
}

namespace {

class BinderResolverTest : public ::testing::Test {
 public:
  BinderResolverTest() {
    factory_ = grpc_core::CoreConfiguration::Get()
                   .resolver_registry()
                   .LookupResolverFactory("binder");
  }
  ~BinderResolverTest() override {}
  static void SetUpTestSuite() {
    grpc_core::CoreConfiguration::Reset();
    grpc_core::CoreConfiguration::BuildSpecialConfiguration(
        [](grpc_core::CoreConfiguration::Builder* builder) {
          BuildCoreConfiguration(builder);
          if (!builder->resolver_registry()->HasResolverFactory("binder")) {
            // Binder resolver will only be registered on platforms that support
            // binder transport. If it is not registered on current platform, we
            // manually register it here for testing purpose.
            RegisterBinderResolver(builder);
            ASSERT_TRUE(
                builder->resolver_registry()->HasResolverFactory("binder"));
          }
        });
    grpc_init();
    if (grpc_core::CoreConfiguration::Get()
            .resolver_registry()
            .LookupResolverFactory("binder") == nullptr) {
    }
  }
  static void TearDownTestSuite() { grpc_shutdown(); }

  void SetUp() override { ASSERT_TRUE(factory_); }

  class ResultHandler : public grpc_core::Resolver::ResultHandler {
   public:
    ResultHandler() = default;

    explicit ResultHandler(const std::string& expected_binder_id)
        : expect_result_(true), expected_binder_id_(expected_binder_id) {}

    void ReportResult(grpc_core::Resolver::Result result) override {
      EXPECT_TRUE(expect_result_);
      ASSERT_TRUE(result.addresses.ok());
      ASSERT_EQ(result.addresses->size(), 1);
      grpc_core::ServerAddress addr = (*result.addresses)[0];
      const struct sockaddr_un* un =
          reinterpret_cast<const struct sockaddr_un*>(addr.address().addr);
      EXPECT_EQ(addr.address().len,
                sizeof(un->sun_family) + expected_binder_id_.length() + 1);
      EXPECT_EQ(un->sun_family, AF_MAX);
      EXPECT_EQ(un->sun_path, expected_binder_id_);
    }

   private:
    // Whether we expect ReportResult function to be invoked
    bool expect_result_ = false;

    std::string expected_binder_id_;
  };

  void TestSucceeds(const char* string, const std::string& expected_path) {
    gpr_log(GPR_DEBUG, "test: '%s' should be valid for '%s'", string,
            std::string(factory_->scheme()).c_str());
    grpc_core::ExecCtx exec_ctx;
    absl::StatusOr<grpc_core::URI> uri = grpc_core::URI::Parse(string);
    ASSERT_TRUE(uri.ok()) << uri.status().ToString();
    grpc_core::ResolverArgs args;
    args.uri = std::move(*uri);
    args.result_handler =
        absl::make_unique<BinderResolverTest::ResultHandler>(expected_path);
    grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
        factory_->CreateResolver(std::move(args));
    ASSERT_TRUE(resolver != nullptr);
    resolver->StartLocked();
  }

  void TestFails(const char* string) {
    gpr_log(GPR_DEBUG, "test: '%s' should be invalid for '%s'", string,
            std::string(factory_->scheme()).c_str());
    grpc_core::ExecCtx exec_ctx;
    absl::StatusOr<grpc_core::URI> uri = grpc_core::URI::Parse(string);
    ASSERT_TRUE(uri.ok()) << uri.status().ToString();
    grpc_core::ResolverArgs args;
    args.uri = std::move(*uri);
    args.result_handler =
        absl::make_unique<BinderResolverTest::ResultHandler>();
    grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
        factory_->CreateResolver(std::move(args));
    EXPECT_TRUE(resolver == nullptr);
  }

 private:
  grpc_core::ResolverFactory* factory_;
};

}  // namespace

// Authority is not allowed
TEST_F(BinderResolverTest, AuthorityPresents) {
  TestFails("binder://example");
  TestFails("binder://google.com");
  TestFails("binder://google.com/test");
}

// Path cannot be empty
TEST_F(BinderResolverTest, EmptyPath) {
  TestFails("binder:");
  TestFails("binder:/");
  TestFails("binder://");
}

TEST_F(BinderResolverTest, PathLength) {
  // Note that we have a static assert in binder_resolver.cc that checks
  // sizeof(sockaddr_un::sun_path) is greater than 100

  // 100 character path should be fine
  TestSucceeds(("binder:l" + std::string(98, 'o') + "g").c_str(),
               "l" + std::string(98, 'o') + "g");

  // 200 character path most likely will fail
  TestFails(("binder:l" + std::string(198, 'o') + "g").c_str());
}

TEST_F(BinderResolverTest, SlashPrefixes) {
  TestSucceeds("binder:///test", "test");
  TestSucceeds("binder:////test", "/test");
}

TEST_F(BinderResolverTest, ValidCases) {
  TestSucceeds("binder:[[", "[[");
  TestSucceeds("binder:google!com", "google!com");
  TestSucceeds("binder:test/", "test/");
  TestSucceeds("binder:test:", "test:");

  TestSucceeds("binder:e", "e");
  TestSucceeds("binder:example", "example");
  TestSucceeds("binder:google.com", "google.com");
  TestSucceeds("binder:~", "~");
  TestSucceeds("binder:12345", "12345");
  TestSucceeds(
      "binder:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._"
      "~",
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~");
}

#endif

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  grpc::testing::TestEnvironment env(&argc, argv);
  return RUN_ALL_TESTS();
}
