/*
 * Copyright (C) 2025 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 <gtest/gtest.h>

#include <aidl/android/hardware/weaver/IWeaver.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android/binder_auto_utils.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>

#include <chrono>
#include <csignal>
#include <future>
#include <string>
#include <thread>

using aidl::android::hardware::weaver::IWeaver;
using aidl::android::hardware::weaver::WeaverConfig;

constexpr char SECURE_ELEMENT_SERVICE_NAME[]
                                = "android.se.omapi.ISecureElementService/default";
constexpr char WEAVER_SERVICE_NAME[] = "android.hardware.weaver.IWeaver/default";

// Helper function to run a command and capture its standard output.
// Returns true on success (exit code 0), false otherwise.
static bool RunCommand(const std::string& command, std::string& stdout_str) {
    stdout_str.clear();
    FILE* pipe = popen(command.c_str(), "r");
    if (!pipe) {
        PLOG(ERROR) << "Failed to popen command: " << command;
        return false;
    }

    if (!android::base::ReadFdToString(fileno(pipe), &stdout_str)) {
        LOG(ERROR) << "Failed to read command output.";
        return false;
    }

    int status = pclose(pipe);
    return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}

// Gets the PID of the Secure Element service using dumpsys.
// Returns the PID as an integer, or <=0 if the service is not running or on error.
static int GetSecureElementServicePid() {
    std::string pid_str;
    const std::string command = "dumpsys --pid " + std::string(SECURE_ELEMENT_SERVICE_NAME);
    if (RunCommand(command, pid_str)) {
        pid_str.erase(pid_str.find_last_not_of(" \n\r\t") + 1);
        if (!pid_str.empty() && std::all_of(pid_str.begin(), pid_str.end(), ::isdigit)) {
            return atoi(pid_str.c_str());
        }
    }
    return -1;
}

// Verifies that the Weaver HAL is responsive by calling its getConfig method.
// Returns an AssertionResult to indicate success or failure.
testing::AssertionResult CheckWeaverHalIsResponding() {
    LOG(INFO) << "Checking if Weaver HAL is responding..";
    ndk::SpAIBinder binder(AServiceManager_waitForService(WEAVER_SERVICE_NAME));
    std::shared_ptr<IWeaver> weaver_service = IWeaver::fromBinder(binder);
    if (weaver_service == nullptr) {
        return testing::AssertionFailure() << "Failed to get Weaver AIDL service.";
    }

    WeaverConfig config;
    ndk::ScopedAStatus status = weaver_service->getConfig(&config);
    if (!status.isOk()) {
        return testing::AssertionFailure()
                    << "Weaver HAL's getConfig() returned an error: "
                    << status.getMessage();
    }
    LOG(INFO) << "Weaver HAL is responding without errors.";
    return testing::AssertionSuccess();
}

static void OnBinderDied(void* promise_cookie) {
    LOG(INFO) << "Received a binder died notification for the service.";
    // Set the promise the test is waiting on.
    std::promise<void>* promise = static_cast<std::promise<void>*>(promise_cookie);
    promise->set_value();
}

static void OnBinderUnlinked(void* promise_cookie) {
    LOG(INFO) << "Cleaning-up the cookie on unlinking death recipient..";
    delete static_cast<std::promise<void>*>(promise_cookie);
}

class WeaverKillSecureElementParamTest : public ::testing::TestWithParam<int> {};

TEST_P(WeaverKillSecureElementParamTest, WeaverRecoversAfterSecureElementServiceRestarts) {
    SCOPED_TRACE(::testing::Message() << "Iteration " << GetParam());
    LOG(INFO) << "Starting iteration " << GetParam();

    // Get the initial PID of the Secure Element Service
    const int initial_pid = GetSecureElementServicePid();
    ASSERT_GT(initial_pid, 0) << "The Secure Element Service is not running.";
    LOG(INFO) << "The Secure Element Service's PID: " << initial_pid;

    // Register a death recipient with the Secure Element Service to confirm
    // that the service triggers a death notification after it is killed
    ndk::SpAIBinder se_binder(AServiceManager_getService(SECURE_ELEMENT_SERVICE_NAME));
    ASSERT_NE(se_binder.get(), nullptr)
        << "Failed to get the Secure Element Service binder.";
    // Use promise/future objects to communicate if a death notification is received.
    // The death recipient will own and set the promise object while the test will
    // wait on the associated future object.
    std::promise<void> *death_promise = new std::promise<void>();
    auto death_future = death_promise->get_future();
    auto death_recipient = ndk::ScopedAIBinder_DeathRecipient(
                                    AIBinder_DeathRecipient_new(OnBinderDied));
    AIBinder_DeathRecipient_setOnUnlinked(death_recipient.get(), OnBinderUnlinked);
    auto link_status = AIBinder_linkToDeath(se_binder.get(), death_recipient.get(),
                                            static_cast<void*>(death_promise));
    ASSERT_EQ(link_status, STATUS_OK) << "AIBinder_linkToDeath() failed.";

    // Kill the Secure Element Service
    LOG(INFO) << "Killing the Secure Element Service..";
    ASSERT_EQ(kill(initial_pid, SIGKILL), 0) << "Failed to kill the service.";

    // Verify that a death notification is received
    LOG(INFO) << "Waiting for a death notification..";
    const int kDeathNotificationTimeoutSec = 5;
    auto future_status = death_future.wait_for(
                            std::chrono::seconds(kDeathNotificationTimeoutSec));
    ASSERT_EQ(future_status, std::future_status::ready)
        << "Did not receive a death notification within "
        << kDeathNotificationTimeoutSec << " seconds.";
    LOG(INFO) << "Successfully received a death notification.";

    // Confirm that the Secure Element Service recovers with a new PID
    int new_pid = 0;
    const int kMaxRetries = 100;  // 100 * 100ms = 10 seconds timeout
    constexpr auto kRetryDelay = std::chrono::milliseconds(100);
    for (int i = 0; i < kMaxRetries; ++i) {
        std::this_thread::sleep_for(kRetryDelay);
        new_pid = GetSecureElementServicePid();
        if (new_pid > 0 && new_pid != initial_pid) {
            break;
        }
    }
    ASSERT_GT(new_pid, 0) << "The Secure Element Service did not restart.";
    ASSERT_NE(new_pid, initial_pid)
            << "The Secure Element Service did not restart (has the same PID).";
    LOG(INFO) << "The Secure Element Service recovered with new PID: " << new_pid;

    // Check that the Weaver HAL is still responding, with a 5-second timeout.
    // A small delay to allow Weaver to process the death notification.
    std::this_thread::sleep_for(std::chrono::seconds(1));
    const int kWeaverHalTimeoutSec = 5;
    auto future = std::async(std::launch::async, CheckWeaverHalIsResponding);
    if (future.wait_for(std::chrono::seconds(kWeaverHalTimeoutSec))
                                            == std::future_status::timeout) {
        FAIL() << "The Weaver HAL did not respond for too long (timeout="
                        << kWeaverHalTimeoutSec << "seconds)";
    } else {
        ASSERT_TRUE(future.get());
    }
}

INSTANTIATE_TEST_SUITE_P(Repeat5Times, WeaverKillSecureElementParamTest, ::testing::Range(1, 6));

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