* Copyright (c) 2026 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 <gtest/gtest.h>
#include <cerrno>
#include <cstring>
#include <string>
#include <vector>
#include "sandbox_dec.h"
#include "dec_config.h"
#include "appspawn_utils.h"
#include "appspawn_hook.h"
#include "securec.h"
using namespace testing;
using namespace testing::ext;
extern "C" {
int SetDecPolicyBatch(int fd, GlobalDecPolicyInfo *decPolicyInfos,
uint64_t timestamp, uint32_t start, uint32_t count);
}
static const int MOCK_OPEN_RETURN_FD = 3;
static const int MOCK_CLOCK_TIME_SEC = 1000;
static const int MOCK_CLOCK_TIME_NSEC = 500;
static const int MOCK_TOKEN_ID = 12345;
static int g_mockIoctlReturn = 0;
static int g_mockIoctlCallCount = 0;
static uint32_t g_mockIoctlFailAfterCount = 0;
static const void *g_lastIoctlData = nullptr;
static int g_mockOpenReturn = MOCK_OPEN_RETURN_FD;
static const char *MOCK_OPEN_PATH = nullptr;
static int g_mockClockTimeSec = MOCK_CLOCK_TIME_SEC;
static int g_mockClockTimeNsec = MOCK_CLOCK_TIME_NSEC;
extern "C" {
int __wrap_open(const char *pathname, int flags, ...)
{
MOCK_OPEN_PATH = pathname;
return g_mockOpenReturn;
}
int __wrap_close(int fd)
{
return 0;
}
int __wrap_AddServerStageHook(AppSpawnHookStage stage, int prio, ServerStageHook hook)
{
return 0;
}
int __wrap_ioctl(int fd, unsigned long request, ...)
{
g_mockIoctlCallCount++;
va_list args;
va_start(args, request);
void *arg = va_arg(args, void *);
va_end(args);
if (g_lastIoctlData != nullptr) {
g_lastIoctlData = arg;
}
g_lastIoctlData = arg;
if (g_mockIoctlFailAfterCount > 0 && g_mockIoctlCallCount > g_mockIoctlFailAfterCount) {
errno = EINVAL;
return -1;
}
return g_mockIoctlReturn;
}
int __wrap_clock_gettime(clockid_t clk_id, struct timespec *ts)
{
if (ts != nullptr) {
ts->tv_sec = g_mockClockTimeSec;
ts->tv_nsec = g_mockClockTimeNsec;
}
return 0;
}
}
namespace OHOS {
class SandboxDecTest : public testing::Test {
public:
static void SetUpTestCase() {}
static void TearDownTestCase() {}
void SetUp()
{
const TestInfo *info = UnitTest::GetInstance()->current_test_info();
GTEST_LOG_(INFO) << info->test_suite_name() << "." << info->name() << " start";
ResetMockState();
}
void TearDown()
{
const TestInfo *info = UnitTest::GetInstance()->current_test_info();
GTEST_LOG_(INFO) << info->test_suite_name() << "." << info->name() << " end";
}
void ResetMockState()
{
g_mockIoctlReturn = 0;
g_mockIoctlCallCount = 0;
g_mockIoctlFailAfterCount = 0;
g_lastIoctlData = nullptr;
g_mockOpenReturn = MOCK_OPEN_RETURN_FD;
MOCK_OPEN_PATH = nullptr;
g_mockClockTimeSec = MOCK_CLOCK_TIME_SEC;
g_mockClockTimeNsec = MOCK_CLOCK_TIME_NSEC;
}
void FillPolicyInfo(DecPolicyInfo &info, uint32_t pathCount, uint32_t pathOffset = 0)
{
errno_t ret = memset_s(&info, sizeof(DecPolicyInfo), 0, sizeof(DecPolicyInfo));
if (ret != EOK) {
return;
}
if (pathCount > KERNEL_BATCH_SIZE) {
pathCount = KERNEL_BATCH_SIZE;
}
info.tokenId = MOCK_TOKEN_ID;
info.pathNum = pathCount;
info.flag = false;
for (uint32_t i = 0; i < pathCount; i++) {
std::string pathStr = "/data/test/path_" + std::to_string(pathOffset + i);
info.path[i].path = strdup(pathStr.c_str());
info.path[i].pathLen = static_cast<uint32_t>(pathStr.length());
info.path[i].mode = 0x1;
info.path[i].flag = false;
}
}
void FreePolicyInfo(DecPolicyInfo &info)
{
for (uint32_t i = 0; i < info.pathNum; i++) {
if (info.path[i].path != nullptr) {
free(info.path[i].path);
info.path[i].path = nullptr;
}
}
}
void FillMultiplePolicyInfos(uint32_t totalPathCount)
{
uint32_t batches = (totalPathCount + KERNEL_BATCH_SIZE - 1) / KERNEL_BATCH_SIZE;
for (uint32_t batch = 0; batch < batches; batch++) {
uint32_t start = batch * KERNEL_BATCH_SIZE;
uint32_t remaining = totalPathCount - start;
uint32_t count = (remaining > KERNEL_BATCH_SIZE) ? KERNEL_BATCH_SIZE : remaining;
DecPolicyInfo info;
FillPolicyInfo(info, count, start);
SetDecPolicyInfos(&info);
FreePolicyInfo(info);
}
}
};
* @tc.name: SandboxDec_SetDecPolicy_8Paths_1Batch_001
* @tc.desc: Verify SetDecPolicy delivers 8 paths in 1 batch (8 <= KERNEL_BATCH_SIZE)
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicy_8Paths_1Batch_001, TestSize.Level1)
{
DecPolicyInfo info;
FillPolicyInfo(info, 8);
SetDecPolicyInfos(&info);
g_mockIoctlCallCount = 0;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 1);
EXPECT_NE(MOCK_OPEN_PATH, nullptr);
FreePolicyInfo(info);
}
* @tc.name: SandboxDec_SetDecPolicy_16Paths_2Batches_001
* @tc.desc: Verify SetDecPolicy delivers 16 paths in 2 batches
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicy_16Paths_2Batches_001, TestSize.Level1)
{
FillMultiplePolicyInfos(16);
g_mockIoctlCallCount = 0;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 2);
}
* @tc.name: SandboxDec_SetDecPolicy_64Paths_8Batches_001
* @tc.desc: Verify SetDecPolicy delivers 64 paths in 8 batches (MAX_POLICY_NUM)
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicy_64Paths_8Batches_001, TestSize.Level1)
{
FillMultiplePolicyInfos(MAX_POLICY_NUM);
g_mockIoctlCallCount = 0;
SetDecPolicy();
uint32_t expectedBatches = (MAX_POLICY_NUM + KERNEL_BATCH_SIZE - 1) / KERNEL_BATCH_SIZE;
EXPECT_EQ(g_mockIoctlCallCount, static_cast<int>(expectedBatches));
EXPECT_EQ(expectedBatches, 8u);
}
* @tc.name: SandboxDec_SetDecPolicy_TimestampConsistency_001
* @tc.desc: Verify all batches use the same timestamp
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicy_TimestampConsistency_001, TestSize.Level1)
{
FillMultiplePolicyInfos(16);
g_mockClockTimeSec = 9999;
g_mockClockTimeNsec = 12345;
g_mockIoctlCallCount = 0;
g_lastIoctlData = nullptr;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 2);
uint64_t expectedTimestamp = (uint64_t)g_mockClockTimeSec * APPSPAWN_SEC_TO_NSEC + (uint64_t)g_mockClockTimeNsec;
EXPECT_EQ(expectedTimestamp, 9999000012345ULL);
}
* @tc.name: SandboxDec_SetDecPolicy_IoctlFailContinue_001
* @tc.desc: Verify SetDecPolicy continues to next batch when one batch fails
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicy_IoctlFailContinue_001, TestSize.Level1)
{
FillMultiplePolicyInfos(24);
g_mockIoctlReturn = 0;
g_mockIoctlFailAfterCount = 1;
g_mockIoctlCallCount = 0;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 3);
}
* @tc.name: SandboxDec_SetDecPolicy_EmptyPolicy_001
* @tc.desc: Verify SetDecPolicy returns safely when g_decPolicyInfos is NULL
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicy_EmptyPolicy_001, TestSize.Level1)
{
g_mockIoctlCallCount = 0;
g_mockOpenReturn = MOCK_OPEN_RETURN_FD;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 0);
}
* @tc.name: SandboxDec_SetDecPolicy_OpenDeviceFail_001
* @tc.desc: Verify SetDecPolicy handles /dev/dec open failure gracefully
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicy_OpenDeviceFail_001, TestSize.Level1)
{
DecPolicyInfo info;
FillPolicyInfo(info, 8);
SetDecPolicyInfos(&info);
g_mockOpenReturn = -1;
g_mockIoctlCallCount = 0;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 0);
FreePolicyInfo(info);
}
* @tc.name: SandboxDec_SetDecPolicyInfos_NormalAdd_001
* @tc.desc: Verify SetDecPolicyInfos correctly stores policy info
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicyInfos_NormalAdd_001, TestSize.Level1)
{
DecPolicyInfo info;
FillPolicyInfo(info, 5);
SetDecPolicyInfos(&info);
g_mockIoctlCallCount = 0;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 1);
FreePolicyInfo(info);
}
* @tc.name: SandboxDec_SetDecPolicyInfos_ExceedLimit_001
* @tc.desc: Verify SetDecPolicyInfos rejects paths exceeding MAX_POLICY_NUM
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicyInfos_ExceedLimit_001, TestSize.Level1)
{
FillMultiplePolicyInfos(MAX_POLICY_NUM);
DecPolicyInfo infoExtra;
FillPolicyInfo(infoExtra, 1);
SetDecPolicyInfos(&infoExtra);
g_mockIoctlCallCount = 0;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 0);
FreePolicyInfo(infoExtra);
}
* @tc.name: SandboxDec_SetDecPolicyBatch_InvalidParams_001
* @tc.desc: Verify SetDecPolicyBatch returns -1 for invalid parameters
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicyBatch_InvalidParams_001, TestSize.Level1)
{
GlobalDecPolicyInfo info;
errno_t rc = memset_s(&info, sizeof(GlobalDecPolicyInfo), 0, sizeof(GlobalDecPolicyInfo));
ASSERT_EQ(rc, EOK);
info.pathNum = 8;
int ret = SetDecPolicyBatch(3, nullptr, 0, 0, KERNEL_BATCH_SIZE);
EXPECT_EQ(ret, -1);
ret = SetDecPolicyBatch(3, &info, 0, 0, 0);
EXPECT_EQ(ret, -1);
ret = SetDecPolicyBatch(3, &info, 0, 0, KERNEL_BATCH_SIZE + 1);
EXPECT_EQ(ret, -1);
}
* @tc.name: SandboxDec_SetDecPolicyInfos_ZeroPathNum_001
* @tc.desc: Verify SetDecPolicyInfos handles zero pathNum safely
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicyInfos_ZeroPathNum_001, TestSize.Level1)
{
DecPolicyInfo info;
errno_t rc = memset_s(&info, sizeof(DecPolicyInfo), 0, sizeof(DecPolicyInfo));
ASSERT_EQ(rc, EOK);
info.pathNum = 0;
SetDecPolicyInfos(&info);
g_mockIoctlCallCount = 0;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 0);
}
* @tc.name: SandboxDec_SetDecPolicy_1Path_001
* @tc.desc: Verify SetDecPolicy delivers 1 path correctly (less than batch size)
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicy_1Path_001, TestSize.Level1)
{
DecPolicyInfo info;
FillPolicyInfo(info, 1);
SetDecPolicyInfos(&info);
g_mockIoctlCallCount = 0;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 1);
FreePolicyInfo(info);
}
* @tc.name: SandboxDec_SetDecPolicy_9Paths_2Batches_001
* @tc.desc: Verify SetDecPolicy delivers 9 paths in 2 batches (cross-batch boundary)
* @tc.type: FUNC
*/
HWTEST_F(SandboxDecTest, SandboxDec_SetDecPolicy_9Paths_2Batches_001, TestSize.Level1)
{
FillMultiplePolicyInfos(9);
g_mockIoctlCallCount = 0;
SetDecPolicy();
EXPECT_EQ(g_mockIoctlCallCount, 2);
}
}