/*
 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
 * 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 "bms_wrapper.h"

#include <cstring>
#include "securec.h"

#include "accesstoken_kit.h"
#include "app_provision_info.h"
#include "bundle_mgr_client.h"
#include "bundle_mgr_interface.h"
#include "hap_token_info.h"
#include "ipc_skeleton.h"
#include "iservice_registry.h"

#include "asset_type.h"
#include "asset_log.h"
#include "system_event_wrapper.h"

using namespace OHOS;
using namespace AppExecFwk;
using namespace Security::AccessToken;

namespace {
constexpr int BUNDLE_MGR_SERVICE_SYS_ABILITY_ID = 401;

const char * const UPGRADE_HAP_LIST[] = ASSET_UPGRADE_HAP_CONFIG;
const char * const CE_UPGRADE_CONFIG = ASSET_CE_UPGRADE_CONFIG;

bool AssetMemCmp(const void *ptr1, const void *ptr2, uint32_t size1, uint32_t size2)
{
    if (size1 != size2) {
        return false;
    }
    return memcmp(ptr1, ptr2, size1) == EOK;
}

sptr<IBundleMgr> GetBundleMgr()
{
    auto systemAbilityManager = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    if (systemAbilityManager == nullptr) {
        LOGE("[FATAL]systemAbilityManager is nullptr, please check.");
        return nullptr;
    }
    auto bundleMgrRemoteObj = systemAbilityManager->GetSystemAbility(BUNDLE_MGR_SERVICE_SYS_ABILITY_ID);
    if (bundleMgrRemoteObj == nullptr) {
        LOGE("[FATAL]bundleMgrRemoteObj is nullptr, please check.");
        return nullptr;
    }
    return iface_cast<IBundleMgr>(bundleMgrRemoteObj);
}

int32_t GetBundleNameAndAppIndex(AssetGroupInfo &assetGroupInfo, ProcessInfo *processInfo)
{
    processInfo->hapInfo.appIndex = assetGroupInfo.appIndex;
    if (memcpy_s(processInfo->processName.data, processInfo->processName.size,
        assetGroupInfo.bundleName.c_str(), assetGroupInfo.bundleName.size()) != EOK) {
        LOGE("[FATAL]The processName buffer is too small. Expect size: %{public}zu, actual size: %{public}u",
            assetGroupInfo.bundleName.size(), processInfo->processName.size);
        return ASSET_OUT_OF_MEMORY;
    }
    processInfo->processName.size = assetGroupInfo.bundleName.size();

    return ASSET_SUCCESS;
}

int32_t GetBundleInfo(AssetGroupInfo &assetGroupInfo, ProcessInfo *processInfo)
{
    if (memcpy_s(processInfo->hapInfo.appId.data, processInfo->hapInfo.appId.size, assetGroupInfo.appId.c_str(),
        assetGroupInfo.appId.size()) != EOK) {
        LOGE("[FATAL]The app id buffer is too small. Expect size: %{public}zu, actual size: %{public}u",
            assetGroupInfo.appId.size(), processInfo->hapInfo.appId.size);
        return ASSET_OUT_OF_MEMORY;
    }
    processInfo->hapInfo.appId.size = assetGroupInfo.appId.size();

    if (processInfo->hapInfo.groupId.data == nullptr || processInfo->hapInfo.groupId.size == 0) {
        return ASSET_SUCCESS;
    }

    for (const std::string &groupId : assetGroupInfo.assetAccessGroups) {
        if (groupId.size() == processInfo->hapInfo.groupId.size &&
            memcmp(processInfo->hapInfo.groupId.data, groupId.data(), processInfo->hapInfo.groupId.size) == 0) {
            LOGI("Found matching group id.");
            return ASSET_SUCCESS;
        }
    }
    LOGE("[FATAL]No matching group id found!");
    return ASSET_INVALID_ARGUMENT;
}

int32_t GetAppProvisionInfo(AssetGroupInfo &assetGroupInfo, ProcessInfo *processInfo)
{
    if (processInfo->hapInfo.developerId.data == nullptr || processInfo->hapInfo.developerId.size == 0) {
        return ASSET_SUCCESS;
    }

    std::string mainDeveloperId;
    size_t pos = assetGroupInfo.developerId.find('.');
    if (pos != std::string::npos) {
        mainDeveloperId = assetGroupInfo.developerId.substr(pos + 1);
    } else {
        mainDeveloperId = assetGroupInfo.developerId;
    }
    if (memcpy_s(processInfo->hapInfo.developerId.data, processInfo->hapInfo.developerId.size, mainDeveloperId.c_str(),
        mainDeveloperId.size()) != EOK) {
        LOGE("[FATAL]The developer id buffer is too small. Expect size: %{public}zu, actual size: %{public}u",
            mainDeveloperId.size(), processInfo->hapInfo.developerId.size);
        return ASSET_OUT_OF_MEMORY;
    }
    processInfo->hapInfo.developerId.size = mainDeveloperId.size();

    return ASSET_SUCCESS;
}

int32_t GetHapProcessInfo(uint32_t userId, uint64_t uid, ProcessInfo *processInfo)
{
    auto bundleMgr = GetBundleMgr();
    if (bundleMgr == nullptr) {
        LOGE("[FATAL]bundleMgr is nullptr, please check.");
        return ASSET_BMS_ERROR;
    }
    AppExecFwk::BundleMgrClient bmsClient;

    AssetGroupInfo assetGroupInfo;
    int32_t ret = bundleMgr->GetAssetGroupsInfo(uid, assetGroupInfo);
    if (ret != ASSET_SUCCESS) {
        LOGE("[FATAL]GetAssetGroupsInfo failed.");
        return ret;
    }

    ret = GetBundleNameAndAppIndex(assetGroupInfo, processInfo);
    if (ret != ASSET_SUCCESS) {
        return ret;
    }

    ret = GetBundleInfo(assetGroupInfo, processInfo);
    if (ret != ASSET_SUCCESS) {
        return ret;
    }

    return GetAppProvisionInfo(assetGroupInfo, processInfo);
}

int32_t GetNativeProcessInfo(uint32_t tokenId, uint64_t uid, ProcessInfo *processInfo)
{
    NativeTokenInfo nativeTokenInfo;
    int32_t ret = AccessTokenKit::GetNativeTokenInfo(tokenId, nativeTokenInfo);
    if (ret != RET_SUCCESS) {
        LOGE("[FATAL]Get native token info failed, ret = %{public}d", ret);
        return ASSET_ACCESS_TOKEN_ERROR;
    }

    if (memcpy_s(processInfo->processName.data, processInfo->processName.size, nativeTokenInfo.processName.c_str(),
        nativeTokenInfo.processName.size()) != EOK) {
        LOGE("[FATAL]The processName buffer is too small. Expect size: %{public}zu, actual size: %{public}u",
            nativeTokenInfo.processName.size(), processInfo->processName.size);
        return ASSET_OUT_OF_MEMORY;
    }
    processInfo->processName.size = nativeTokenInfo.processName.size();
    processInfo->nativeInfo.uid = uid;

    return ASSET_SUCCESS;
}

void MarkGroupAsModified(const std::string &groupId, MutAssetBlobArray *groupIds)
{
    if (groupIds == nullptr || groupIds->blob == nullptr || groupIds->size == 0) {
        return;
    }
    for (uint32_t i = 0; i < groupIds->size; i++) {
        if (strcmp(groupId.c_str(), groupIds->blob[i].blob) == 0) {
            groupIds->blob[i].modify = true;
            break;
        }
    }
}

void ProcessBundleInfos(const std::vector<AppExecFwk::BundleInfo> &bundleInfos,
    int32_t userId, MutAssetBlobArray *groupIds)
{
    std::unordered_set<std::string> targetGroupIds;
    for (uint32_t i = 0; i < groupIds->size; i++) {
        targetGroupIds.insert(groupIds->blob[i].blob);
    }

    for (const AppExecFwk::BundleInfo &bundleInfo : bundleInfos) {
        for (const std::string &groupId : bundleInfo.applicationInfo.assetAccessGroups) {
            if (targetGroupIds.find(groupId) != targetGroupIds.end()) {
                LOGI("Found matching group id. Do not remove data in this group");
                MarkGroupAsModified(groupId, groupIds);
            }
        }
    }
}

int32_t ProcessGroupIds(AssetGroupInfo &assetGroupInfo, MutAssetBlobArrayChangeable *groupIds)
{
    size_t i = 0;
    for (const std::string &groupId : assetGroupInfo.assetAccessGroups) {
        if (memcpy_s(groupIds->blob[i].data, groupIds->blob[i].size,
            groupId.c_str(), groupId.size()) != EOK) {
            LOGE("[FATAL]The grooupId buffer is too small. Expect size: %{public}zu, actual size: %{public}u",
                groupId.size(), groupIds->blob[i].size);
            return ASSET_OUT_OF_MEMORY;
        }
        groupIds->blob[i].size = groupId.size();
        i++;
    }
    groupIds->size = i;
    return ASSET_SUCCESS;
}

int32_t ProcessDeveloperId(AssetGroupInfo &assetGroupInfo, Asset_Blob *developerId)
{
    if (developerId->data == nullptr || developerId->size == 0) {
        return ASSET_BMS_ERROR;
    }

    std::string mainDeveloperId;
    size_t pos = assetGroupInfo.developerId.find('.');
    if (pos != std::string::npos) {
        mainDeveloperId = assetGroupInfo.developerId.substr(pos + 1);
    } else {
        mainDeveloperId = assetGroupInfo.developerId;
    }
    if (memcpy_s(developerId->data, developerId->size, mainDeveloperId.c_str(),
        mainDeveloperId.size()) != EOK) {
        LOGE("[FATAL]The developer id buffer is too small. Expect size: %{public}zu, actual size: %{public}u",
            mainDeveloperId.size(), developerId->size);
        return ASSET_OUT_OF_MEMORY;
    }
    developerId->size = mainDeveloperId.size();

    return ASSET_SUCCESS;
}
} // namespace

int32_t GetCallingProcessInfo(uint32_t userId, uint64_t uid, ProcessInfo *processInfo, bool preload)
{
    if (processInfo == nullptr) {
        return ASSET_INVALID_ARGUMENT;
    }
    processInfo->userId = userId;
    auto tokenId = IPCSkeleton::GetCallingTokenID();
    ATokenTypeEnum tokenType = ATokenTypeEnum::TOKEN_HAP;
    if (!preload) {
        tokenType = AccessTokenKit::GetTokenTypeFlag(tokenId);
    }
    int32_t res = ASSET_SUCCESS;
    switch (tokenType) {
        case ATokenTypeEnum::TOKEN_HAP:
            if (processInfo->hapInfo.groupId.data != nullptr) {
                processInfo->ownerType = HAP_GROUP;
            } else {
                processInfo->ownerType = HAP;
            }
            res = GetHapProcessInfo(userId, uid, processInfo);
            break;
        case ATokenTypeEnum::TOKEN_NATIVE:
        case ATokenTypeEnum::TOKEN_SHELL:
            processInfo->ownerType = NATIVE;
            res = GetNativeProcessInfo(tokenId, uid, processInfo);
            break;
        default:
            LOGE("[FATAL]Invalid calling type: %{public}d", tokenType);
            res = ASSET_INVALID_ARGUMENT;
    }
    return res;
}

int32_t GetCloneAppIndexes(int32_t userId, int32_t *appIndexes, uint32_t *indexSize, const char *appName)
{
    auto bundleMgr = GetBundleMgr();
    if (bundleMgr == nullptr) {
        LOGE("[FATAL]bundleMgr is nullptr, please check.");
        return ASSET_BMS_ERROR;
    }
    std::vector<int32_t> indexes;
    int32_t ret = bundleMgr->GetCloneAppIndexes(appName, indexes, userId);
    if (ret != ASSET_SUCCESS) {
        LOGE("Get clone app indexes failed.");
        return ASSET_BMS_ERROR;
    }
    if (*indexSize < indexes.size()) {
        LOGE("Too short index size.");
        return ASSET_INVALID_ARGUMENT;
    }
    
    for (size_t i = 0; i < indexes.size(); i++) {
        *(appIndexes + i) = indexes[i];
    }
    *indexSize = indexes.size();
    return ASSET_SUCCESS;
}

int32_t IsHapInAllowList(int32_t userId, const char *appName, bool *is_in_list)
{
    if (appName == nullptr) {
        LOGE("[FATAL]App name is null.");
        return ASSET_INVALID_ARGUMENT;
    }
    AppExecFwk::BundleInfo bundleInfo;
    AppExecFwk::BundleMgrClient bmsClient;
    if (!bmsClient.GetBundleInfo(appName, BundleFlag::GET_BUNDLE_WITH_HASH_VALUE, bundleInfo, userId)) {
        LOGE("[FATAL]Get bundle info failed!");
        return ASSET_BMS_ERROR;
    }
    for (uint32_t i = 0; i < ARRAY_SIZE(UPGRADE_HAP_LIST); ++i) {
        if (AssetMemCmp(UPGRADE_HAP_LIST[i],
            bundleInfo.appId.c_str(),
            strlen(UPGRADE_HAP_LIST[i]),
            bundleInfo.appId.size())) {
            *is_in_list = true;
            return ASSET_SUCCESS;
        }
    }
    *is_in_list = false;
    return ASSET_SUCCESS;
}

int32_t GetUninstallGroups(int32_t userId, ConstAssetBlob *developerId, MutAssetBlobArray *groupIds)
{
    if (developerId == nullptr || groupIds == nullptr) {
        return ASSET_BMS_ERROR;
    }
    auto bundleMgr = GetBundleMgr();
    if (bundleMgr == nullptr) {
        LOGE("[FATAL]bundleMgr is nullptr, please check.");
        return ASSET_BMS_ERROR;
    }

    std::string useDeveloperId(reinterpret_cast<const char*>(developerId->data), developerId->size);
    std::vector<AppExecFwk::BundleInfo> bundleInfos;
    int32_t ret = bundleMgr->GetAllBundleInfoByDeveloperId(useDeveloperId, bundleInfos, userId);
    if (ret != RET_SUCCESS && ret != ERR_BUNDLE_MANAGER_INVALID_DEVELOPERID) {
        LOGE("[FATAL]GetAllBundleInfoByDeveloperId failed. ret:%{public}d", ret);
        return ASSET_BMS_ERROR;
    }

    ProcessBundleInfos(bundleInfos, userId, groupIds);
    return ASSET_SUCCESS;
}

const char *GetCeUpgradeInfo()
{
    return CE_UPGRADE_CONFIG;
}

int32_t GetCallingHapGroups(uint64_t uid, MutAssetBlobArrayChangeable *groupIds, Asset_Blob *developerId)
{
    auto bundleMgr = GetBundleMgr();
    if (bundleMgr == nullptr) {
        LOGE("[FATAL]bundleMgr is nullptr, please check.");
        return ASSET_BMS_ERROR;
    }
    AppExecFwk::BundleMgrClient bmsClient;

    AssetGroupInfo assetGroupInfo;
    int32_t ret = bundleMgr->GetAssetGroupsInfo(uid, assetGroupInfo);
    if (ret != ASSET_SUCCESS) {
        LOGE("[FATAL]GetAssetGroupsInfo failed. ret: %{public}d", ret);
        return ASSET_BMS_ERROR;
    }
    ret = ProcessGroupIds(assetGroupInfo, groupIds);
    if (ret != ASSET_SUCCESS) {
        LOGE("[FATAL]ProcessGroupIds failed. ret: %{public}d", ret);
        return ret;
    }

    return ProcessDeveloperId(assetGroupInfo, developerId);
}