/*
 * Copyright (C) 2021 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 <lib/tipc/tipc.h>
#include <lib/unittest/unittest.h>
#include <trusty_unittest.h>
#include <uapi/err.h>

#include <cfi-crasher.h>
#include <cfi_crasher_consts.h>

#define TLOG_TAG "cfi-test"

/*
 * Sends command to crasher app. Call crasher_wait after to wait for a
 * reply or channel close. On success, returns zero.
 */
static int crasher_command(handle_t chan, enum crasher_command cmd) {
    int ret;
    struct crasher_msg msg = {
            .cmd = cmd,
    };

    ret = tipc_send1(chan, &msg, sizeof(msg));
    ASSERT_GE(ret, 0);
    ASSERT_EQ(ret, sizeof(msg));

    return 0;

test_abort:
    /* Use ERR_IO to indicate internal error with the test app */
    return ERR_IO;
}

/*
 * This function should always be called after crasher_command since it
 * assumes the test app has already sent a message to the crasher app
 * with the command. In the non-crashing case, returns a positive integer
 * equaling the message command echoed back by the server.
 */
static int crasher_wait(handle_t chan) {
    int ret;
    struct uevent evt;
    ret = wait(chan, &evt, INFINITE_TIME);
    if (ret) { /* error while waiting on channel */
        return ret;
    }
    /*
     * Receiving message and crashing the app should never happen with
     * CFI enabled. If both IPC_HANDLE_POLL_MSG and IPC_HANDLE_POLL_HUP
     * are set, either an internal error occurred or CRASHER_NOT_ENTRY
     * ran with CFI not enabled. We expect exactly one of IPC_HANDLE_POLL_MSG
     * and IPC_HANDLE_POLL_HUP to be set.
     */
    if (evt.event & IPC_HANDLE_POLL_HUP) {
        ASSERT_EQ(evt.event & IPC_HANDLE_POLL_MSG, 0);
        return ERR_CHANNEL_CLOSED;
    }
    ASSERT_NE(evt.event & IPC_HANDLE_POLL_MSG, 0);

    struct crasher_msg msg;
    ret = tipc_recv1(chan, sizeof(msg), &msg, sizeof(msg));
    if (ret < 0) {
        return ret;
    }
    ASSERT_EQ(ret, sizeof(msg));
    /*
     * Return the cmd if we reach this point, that is the app did not crash.
     * If the crasher did not crash, it should echo back the original command,
     * or CRASHER_VOID in the special case where crasher_void executes on
     * the server side.
     */
    return msg.cmd;

test_abort:
    /* Use ERR_IO to indicate internal error with the test app */
    return ERR_IO;
}

typedef struct cfi_crash {
    handle_t chan;
} cfi_crash_t;

#ifdef CFI_ENABLED
#define CHECK_CFI_ENABLED
#else
#define CHECK_CFI_ENABLED                                              \
    {                                                                  \
        trusty_unittest_printf("[   INFO   ] CFI is not supported\n"); \
        GTEST_SKIP();                                                  \
    }
#endif

TEST_F_SETUP(cfi_crash) {
    CHECK_CFI_ENABLED
    _state->chan = INVALID_IPC_HANDLE;
    ASSERT_EQ(tipc_connect(&_state->chan, CFI_CRASHER_PORT), 0);

test_abort:;
}

TEST_F_TEARDOWN(cfi_crash) {
    close(_state->chan);
}

TEST_F(cfi_crash, nop) {
    EXPECT_EQ(crasher_command(_state->chan, CRASHER_NOP), 0);
    EXPECT_EQ(crasher_wait(_state->chan), CRASHER_NOP);
}

TEST_F(cfi_crash, correct) {
    EXPECT_EQ(crasher_command(_state->chan, CRASHER_CORRECT), 0);
    EXPECT_EQ(crasher_wait(_state->chan), CRASHER_CORRECT);
}

TEST_F(cfi_crash, entry) {
    /*
     * CRASHER_ENTRY suppresses the second reply from crasher_void
     * so we only expect one message back at the end of crasher_on_message.
     */
    EXPECT_EQ(crasher_command(_state->chan, CRASHER_ENTRY), 0);
    EXPECT_EQ(crasher_wait(_state->chan), CRASHER_ENTRY);
}

TEST_F(cfi_crash, exclude_wrong_type) {
    EXPECT_EQ(crasher_command(_state->chan, CRASHER_EXCLUDE_WRONG_TYPE), 0);
    EXPECT_EQ(crasher_wait(_state->chan), CRASHER_EXCLUDE_WRONG_TYPE);
}

TEST_F(cfi_crash, exclude_not_entry) {
    /*
     * CRASHER_EXCLUDE_NOT_ENTRY expects one event with a message and a
     * subsequent event indicating the channel is closed.
     */
    EXPECT_EQ(crasher_command(_state->chan, CRASHER_EXCLUDE_NOT_ENTRY), 0);
    EXPECT_EQ(crasher_wait(_state->chan), CRASHER_VOID);
    EXPECT_EQ(crasher_wait(_state->chan), ERR_CHANNEL_CLOSED);
}

TEST_F(cfi_crash, wrong_type) {
    EXPECT_EQ(crasher_command(_state->chan, CRASHER_WRONG_TYPE), 0);
    EXPECT_EQ(crasher_wait(_state->chan), ERR_CHANNEL_CLOSED);
}

TEST_F(cfi_crash, not_entry) {
    EXPECT_EQ(crasher_command(_state->chan, CRASHER_NOT_ENTRY), 0);
    EXPECT_EQ(crasher_wait(_state->chan), ERR_CHANNEL_CLOSED);
}

PORT_TEST(cfi_crash, "com.android.trusty.cfitest")
