/* -------------------------------------------------------------------------
 *  This file is part of the oGRAC project.
 * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
 *
 * oGRAC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 *
 * knl_page.c
 *
 *
 * IDENTIFICATION
 * src/kernel/buffer/knl_page.c
 *
 * -------------------------------------------------------------------------
 */
#include "knl_buffer_module.h"
#include "knl_page.h"
#include "cm_file.h"
#include "cm_kmc.h"
#include "knl_context.h"
#include "knl_undo.h"
#include "pcr_btree.h"

#ifdef __cplusplus
extern "C" {
#endif

void page_init(knl_session_t *session, page_head_t *page, page_id_t id, page_type_t type)
{
    page_tail_t *tail = NULL;
    uint32 size = session->kernel->db.datafiles[id.file].ctrl->block_size;
    errno_t ret;

    ret = memset_sp(page, DEFAULT_PAGE_SIZE(session), 0, DEFAULT_PAGE_SIZE(session));
    knl_securec_check(ret);
    TO_PAGID_DATA(id, page->id);
    TO_PAGID_DATA(INVALID_PAGID, page->next_ext);
    page->size_units = page_size_units(size);
    page->type = type;
    tail = PAGE_TAIL(page);
    tail->checksum = 0;
    tail->pcn = 0;
}

void page_free(knl_session_t *session, page_head_t *page)
{
    page_tail_t *tail = NULL;
    page_id_t next_ext;
    page_id_t id;
    errno_t ret;

    id = AS_PAGID(page->id);
    next_ext = AS_PAGID(page->next_ext);

    ret = memset_sp(page, DEFAULT_PAGE_SIZE(session), 0, DEFAULT_PAGE_SIZE(session));
    knl_securec_check(ret);

    TO_PAGID_DATA(id, page->id);
    TO_PAGID_DATA(next_ext, page->next_ext);
    page->size_units = page_size_units(DEFAULT_PAGE_SIZE(session));
    page->type = PAGE_TYPE_FREE_PAGE;
    tail = PAGE_TAIL(page);
    tail->pcn = 0;
}

status_t page_cipher_reserve_size(knl_session_t *session, encrypt_version_t version, uint8 *cipher_reserve_size)
{
    uint32 page_cost_size = DEFAULT_PAGE_SIZE(session) - sizeof(page_head_t) - sizeof(page_tail_t);
    uint32 max_cipher_len;

    if (cm_get_cipher_len(page_cost_size, &max_cipher_len) != OG_SUCCESS) {
        OG_LOG_RUN_ERR("get cipher len failed");
        return OG_ERROR;
    }

    uint32 max_size = CM_ALIGN4(max_cipher_len - page_cost_size + sizeof(cipher_ctrl_t));
    TO_UINT8_OVERFLOW_CHECK(max_size, uint32);

    *cipher_reserve_size = max_size;
    CM_SAVE_STACK(session->stack);

    char *plain_buf = (char *)cm_push(session->stack, DEFAULT_PAGE_SIZE(session));
    uint32 plain_len = page_cost_size - max_size;
    char *cipher_buf = (char *)cm_push(session->stack, DEFAULT_PAGE_SIZE(session));
    uint32 cipher_len = DEFAULT_PAGE_SIZE(session);

    status_t status = cm_encrypt_impl(plain_buf, plain_len, cipher_buf, &cipher_len);
    if (status != OG_SUCCESS) {
        OG_LOG_RUN_ERR("fail to try encrypt");
        CM_RESTORE_STACK(session->stack);
        return OG_ERROR;
    }

    uint32 real_size = cipher_len - plain_len;
    TO_UINT8_OVERFLOW_CHECK(real_size, uint32);

    if ((uint32)(real_size + sizeof(cipher_ctrl_t)) > max_size) {
        OG_LOG_RUN_ERR("real size %u more than max_size %u.", (uint32)(real_size + sizeof(cipher_ctrl_t)), max_size);
        CM_RESTORE_STACK(session->stack);
        return OG_ERROR;
    }

    CM_RESTORE_STACK(session->stack);
    return OG_SUCCESS;
}

static cipher_ctrl_t *page_cipher_ctrl(page_head_t *page)
{
    uint32 ctrl_offset = 0;
    switch (page->type) {
        case PAGE_TYPE_HEAP_DATA:
        case PAGE_TYPE_PCRH_DATA:
            ctrl_offset = sizeof(heap_page_t);
            break;
        case PAGE_TYPE_BTREE_NODE:
        case PAGE_TYPE_PCRB_NODE:
            ctrl_offset = sizeof(btree_page_t);
            break;
        case PAGE_TYPE_UNDO:
            ctrl_offset = sizeof(undo_page_t);
            break;
        case PAGE_TYPE_LOB_DATA:
            ctrl_offset = PAGE_SIZE(*page) - sizeof(page_tail_t) - sizeof(cipher_ctrl_t);
            break;
        default:
            OG_LOG_RUN_ERR("page type %d not support encrypt.", page->type);
            return NULL;
    }
    return (cipher_ctrl_t *)((char *)page + ctrl_offset);
}

static uint8 page_cipher_offset(page_head_t *page)
{
    uint8 cipher_offset = 0;
    switch (page->type) {
        case PAGE_TYPE_HEAP_DATA:
        case PAGE_TYPE_PCRH_DATA:
            cipher_offset = sizeof(heap_page_t) + sizeof(cipher_ctrl_t);
            break;
        case PAGE_TYPE_BTREE_NODE:
        case PAGE_TYPE_PCRB_NODE:
            cipher_offset = sizeof(btree_page_t) + sizeof(cipher_ctrl_t);
            break;
        case PAGE_TYPE_LOB_DATA:
            cipher_offset = sizeof(lob_data_page_t);
            break;
        case PAGE_TYPE_UNDO:
            cipher_offset = sizeof(undo_page_t) + sizeof(cipher_ctrl_t);
            break;
        default:
            OG_LOG_RUN_ERR("[GET CIPHER OFFSET ERROR]page type %d not support.", page->type);
            break;
    }

    return cipher_offset;
}

static char *page_plain_buf(page_head_t *page, uint8 cipher_reserve_size, uint32 *offset_len)
{
    char *plain_buf = NULL;

    if (*offset_len != 0) {
        return NULL;
    }

    switch (page->type) {
        case PAGE_TYPE_HEAP_DATA:
        case PAGE_TYPE_PCRH_DATA:
            *offset_len = sizeof(heap_page_t) + cipher_reserve_size;
            break;
        case PAGE_TYPE_BTREE_NODE:
        case PAGE_TYPE_PCRB_NODE:
            *offset_len = sizeof(btree_page_t) + cipher_reserve_size;
            break;
        case PAGE_TYPE_LOB_DATA:
            *offset_len = sizeof(lob_data_page_t);
            break;
        case PAGE_TYPE_UNDO:
            *offset_len = sizeof(undo_page_t) + cipher_reserve_size;
            break;
        default:
            OG_LOG_RUN_ERR("page type %d not support encrypt.", page->type);
            break;
    }

    plain_buf = (char *)page + *offset_len;

    return plain_buf;
}

static uint32 page_plain_len(knl_session_t *session, page_head_t *page, uint8 cipher_reserve_size)
{
    uint32 page_meta_size = 0;
    uint32 page_left_size = DEFAULT_PAGE_SIZE(session) - sizeof(page_tail_t) - cipher_reserve_size;

    switch (page->type) {
        case PAGE_TYPE_HEAP_DATA:
        case PAGE_TYPE_PCRH_DATA:
            page_meta_size = sizeof(heap_page_t);
            break;
        case PAGE_TYPE_BTREE_NODE:
        case PAGE_TYPE_PCRB_NODE:
            page_meta_size = sizeof(btree_page_t);
            break;
        case PAGE_TYPE_LOB_DATA:
            page_meta_size = sizeof(lob_data_page_t);
            break;
        case PAGE_TYPE_UNDO:
            page_meta_size = sizeof(undo_page_t);
            break;
        default:
            OG_LOG_RUN_ERR("page type %d not support encrypt.", page->type);
            break;
    }

    return page_left_size - page_meta_size;
}

#ifdef LOG_DIAG
static char *page_reserved_cipher_buf(uint16 cipher_offset, page_head_t *page, uint8 cipher_reserve_size)
{
    char *reserved_buf = NULL;
    switch (page->type) {
        case PAGE_TYPE_HEAP_DATA:
        case PAGE_TYPE_PCRH_DATA:
        case PAGE_TYPE_BTREE_NODE:
        case PAGE_TYPE_PCRB_NODE:
        case PAGE_TYPE_UNDO:
            reserved_buf = ((char *)page + cipher_offset);
            break;
        case PAGE_TYPE_LOB_DATA:
            reserved_buf = ((char *)page + PAGE_SIZE(*page) - sizeof(page_tail_t) - cipher_reserve_size);
            break;
        default:
            knl_panic_log(0, "page type is unknown, panic info: page %u-%u type %u", AS_PAGID(page->id).file,
                          AS_PAGID(page->id).page, page->type);
            break;
    }

    return reserved_buf;
}

static void check_ctrl_befor_encrypt(cipher_ctrl_t *cipher_ctrl, page_head_t *page,
    page_id_t page_id, uint8 cipher_reserve_size)
{
    if (page->encrypted != 0 ||
        cipher_ctrl->cipher_expanded_size != 0 ||
        cipher_ctrl->encrypt_version != 0 ||
        cipher_ctrl->offset != 0 ||
        cipher_ctrl->reserved != 0) {
        knl_panic_log(OG_FALSE, "invalid cipher ctrl before encrypt: "
            "page_info: page %u, file %u, page_type %u,"
            "cipher_ctrl: encrypted: %u, encrypt_version: %u, cipher_expanded_size: %u, offset: %u, plain_cks: %u, "
            "space->ctrl->cipher_reserve_size: %u ",
            page_id.page, page_id.file, page->type, page->encrypted, cipher_ctrl->encrypt_version,
            cipher_ctrl->cipher_expanded_size, cipher_ctrl->offset, cipher_ctrl->plain_cks,
            cipher_reserve_size);
    }
}

static void check_ctrl_after_encrypt(cipher_ctrl_t *cipher_ctrl, page_head_t *page,
    page_id_t page_id, uint32 cipher_len, uint32 plain_len, uint8 cipher_reserve_size)
{
    if (cipher_ctrl->cipher_expanded_size + sizeof(cipher_ctrl_t) > cipher_reserve_size ||
        cipher_len + sizeof(cipher_ctrl_t) > plain_len + cipher_reserve_size) {
        knl_panic_log(OG_FALSE, "invalid cipher ctrl after encrypt :"
            "page_info: page %u, file %u, page_type %u, "
            "cipher_ctrl: encrypted: %u, encrypt_version: %u, cipher_expanded_size: %u, offset: %u, plain_cks: %u, "
            "space->ctrl->cipher_reserve_size: %u ",
            page_id.page, page_id.file, page->type, page->encrypted, cipher_ctrl->encrypt_version,
            cipher_ctrl->cipher_expanded_size, cipher_ctrl->offset, cipher_ctrl->plain_cks,
            cipher_reserve_size);
    }
}

static void check_reserve_ciper_buf(page_head_t *page, page_id_t page_id,
    cipher_ctrl_t *cipher_ctrl, uint8 cipher_reserve_size)
{
    uint16 cipher_offset = page_cipher_offset(page);
    char *reserved_cipher_buf = page_reserved_cipher_buf(cipher_offset, page, cipher_reserve_size);
    uint8 reserved_cipher_size = cipher_reserve_size - sizeof(cipher_ctrl_t);

    for (int i = 0; i < reserved_cipher_size; i++) {
        if (reserved_cipher_buf[i] == 0) {
            continue;
        }
        knl_panic_log(0, "invalid reserved cipher buf. "
            "page_info: page %u, file %u, page_type %u, "
            "cipher_ctrl: encrypted: %u, encrypt_version: %u, cipher_expanded_size: %u, offset: %u, plain_cks: %u, "
            "space->ctrl->cipher_reserve_size: %u ",
            page_id.page, page_id.file, page->type, page->encrypted, cipher_ctrl->encrypt_version,
            cipher_ctrl->cipher_expanded_size, cipher_ctrl->offset, cipher_ctrl->plain_cks,
            cipher_reserve_size);
    }
}

static void check_ctrl_before_decrypt(space_t *space, cipher_ctrl_t *cipher_ctrl, page_head_t *page,
    page_id_t page_id, uint32 cipher_len, uint32 org_plain_len)
{
    if (!page->encrypted ||
        cipher_ctrl->cipher_expanded_size == 0 ||
        cipher_ctrl->encrypt_version == NO_ENCRYPT ||
        cipher_ctrl->cipher_expanded_size + sizeof(cipher_ctrl_t) > space->ctrl->cipher_reserve_size ||
        cipher_len > org_plain_len + space->ctrl->cipher_reserve_size) {
        knl_panic_log(OG_FALSE, "invalid cipher ctrl before decrypt : "
            "page_info: page %u, file %u, page_type %u,"
            "cipher_ctrl: encrypted: %u, encrypt_version: %u, cipher_expanded_size: %u, offset: %u, plain_cks: %u, "
            "space->ctrl->cipher_reserve_size: %u ",
            page_id.page, page_id.file, page->type, page->encrypted, cipher_ctrl->encrypt_version,
            cipher_ctrl->cipher_expanded_size, cipher_ctrl->offset, cipher_ctrl->plain_cks,
            space->ctrl->cipher_reserve_size);
    }
}
#endif

status_t page_encrypt(knl_session_t *session, page_head_t *page, uint8 encrypt_version, uint8 cipher_reserve_size)
{
    cipher_ctrl_t *cipher_ctrl = page_cipher_ctrl(page);
    cipher_ctrl->plain_cks = 0;

#ifdef LOG_DIAG
    page_id_t page_id = AS_PAGID(page->id);
    if (page->type == PAGE_TYPE_UNDO) {
        knl_panic_log(undo_valid_encrypt(session, page), "undo space is not encrypt, panic info: page %u-%u type %u",
                      page_id.file, page_id.page, page->type);
    }
    check_ctrl_befor_encrypt(cipher_ctrl, page, page_id, cipher_reserve_size);
    check_reserve_ciper_buf(page, page_id, cipher_ctrl, cipher_reserve_size);
    page_calc_checksum(page, DEFAULT_PAGE_SIZE(session));
    cipher_ctrl->plain_cks = PAGE_CHECKSUM(page, DEFAULT_PAGE_SIZE(session));
#endif

    CM_SAVE_STACK(session->stack);
    char *cipher_buf = (char *)cm_push(session->stack, DEFAULT_PAGE_SIZE(session));
    uint32 cipher_len = DEFAULT_PAGE_SIZE(session);
    uint32 offset_len = 0;
    char *plain_buf = page_plain_buf(page, cipher_reserve_size, &offset_len);
    uint32 plain_len = page_plain_len(session, page, cipher_reserve_size);

    status_t status = cm_encrypt_impl(plain_buf, plain_len, cipher_buf, &cipher_len);
    if (status != OG_SUCCESS) {
        OG_LOG_RUN_ERR("page encrypt failed.");
        CM_RESTORE_STACK(session->stack);
        return OG_ERROR;
    }

    page->encrypted = OG_TRUE;
    cipher_ctrl->cipher_expanded_size = cipher_len - plain_len;
    cipher_ctrl->offset = page_cipher_offset(page);
    cipher_ctrl->encrypt_version = encrypt_version;
    cipher_ctrl->reserved = 0;

#ifdef LOG_DIAG
    check_ctrl_after_encrypt(cipher_ctrl, page, page_id, cipher_len, plain_len, cipher_reserve_size);
#endif

    errno_t ret = memcpy_sp((char *)page + cipher_ctrl->offset, DEFAULT_PAGE_SIZE(session) - cipher_ctrl->offset,
        cipher_buf, cipher_len);
    knl_securec_check(ret);

    if (session->kernel->attr.db_block_checksum == CKS_FULL) {
        page_calc_checksum(page, DEFAULT_PAGE_SIZE(session));
    }
    CM_RESTORE_STACK(session->stack);
    return OG_SUCCESS;
}

#ifdef LOG_DIAG
static void page_checksum_after_decrypt(knl_session_t *session, page_head_t *page, cipher_ctrl_t *cipher_ctrl,
    cipher_ctrl_t *temp_ctrl, uint8 cipher_reserve_size)
{
    page_id_t page_id = AS_PAGID(page->id);
    PAGE_CHECKSUM(page, DEFAULT_PAGE_SIZE(session)) = cipher_ctrl->plain_cks;
    cipher_ctrl->plain_cks = 0;
    if (!page_verify_checksum(page, DEFAULT_PAGE_SIZE(session))) {
        knl_panic_log(OG_FALSE, "invaid page plain data: "
            "page_info: page %u, file %u, page_type %u, "
            "cipher_ctrl: encrypted: %u, encrypt_version: %u, cipher_expanded_size: %u, offset: %u, plain_cks: %u, "
            "space->ctrl->cipher_reserve_size: %u ",
            page_id.page, page_id.file, page->type, page->encrypted, temp_ctrl->encrypt_version,
            temp_ctrl->cipher_expanded_size, temp_ctrl->offset, temp_ctrl->plain_cks,
            cipher_reserve_size);
    }

    if (page->type == PAGE_TYPE_UNDO) {
        knl_panic_log(undo_valid_encrypt(session, page), "undo space is not encrypt, panic info: page %u-%u type %u",
                      page_id.file, page_id.page, page->type);
    }
}
#endif

status_t page_decrypt(knl_session_t *session, page_head_t *page)
{
    space_t *space = SPACE_GET(session, DATAFILE_GET(session, AS_PAGID_PTR(page->id)->file)->space_id);
    uint8 cipher_reserve_size = space->ctrl->cipher_reserve_size;
    cipher_ctrl_t *cipher_ctrl = page_cipher_ctrl(page);
    uint32 offset_len = 0;
    char *org_plain_buf = page_plain_buf(page, cipher_reserve_size, &offset_len);
    uint32 org_plain_len = page_plain_len(session, page, cipher_reserve_size);
    uint32 cipher_len = org_plain_len + cipher_ctrl->cipher_expanded_size;

#ifdef LOG_DIAG
    page_id_t page_id = AS_PAGID(page->id);
    knl_panic_log(page_type_suport_encrypt(page->type), "current page type is not suport encrypt, panic info: "
                  "page %u-%u type %u", AS_PAGID(page->id).file, AS_PAGID(page->id).page, page->type);
    check_ctrl_before_decrypt(space, cipher_ctrl, page, page_id, cipher_len, org_plain_len);
#endif

    CM_SAVE_STACK(session->stack);
    char *plain_buf = (char *)cm_push(session->stack, DEFAULT_PAGE_SIZE(session));
    uint32 plain_len = DEFAULT_PAGE_SIZE(session);
    if (cm_decrypt_impl((char *)page + cipher_ctrl->offset, cipher_len, plain_buf, &plain_len) != OG_SUCCESS) {
        OG_LOG_RUN_ERR("page decrypt failed");
        CM_RESTORE_STACK(session->stack);
        return OG_ERROR;
    }

    knl_panic_log(plain_len == org_plain_len, "the plain_len is not equal org_plain_len, panic info: "
                  "page %u-%u type %u plain_len %u org_plain_len %u",
                  AS_PAGID(page->id).file, AS_PAGID(page->id).page, page->type, plain_len, org_plain_len);
    errno_t ret = memcpy_sp(org_plain_buf, DEFAULT_PAGE_SIZE(session) - offset_len, plain_buf, plain_len);
    knl_securec_check(ret);

#ifdef LOG_DIAG
    cipher_ctrl_t temp_ctrl = *cipher_ctrl;
    char *reserved_cipher_buf = page_reserved_cipher_buf(cipher_ctrl->offset, page, cipher_reserve_size);
    uint8 reserved_cipher_size = space->ctrl->cipher_reserve_size - sizeof(cipher_ctrl_t);
    ret = memset_sp(reserved_cipher_buf, reserved_cipher_size, 0, reserved_cipher_size);
    knl_securec_check(ret);
#endif

    cipher_ctrl->cipher_expanded_size = 0;
    page->encrypted = OG_FALSE;
    cipher_ctrl->encrypt_version = 0;
    cipher_ctrl->offset = 0;
    cipher_ctrl->reserved = 0;

#ifdef LOG_DIAG
    page_checksum_after_decrypt(session, page, cipher_ctrl, &temp_ctrl, cipher_reserve_size);
#endif
    cipher_ctrl->plain_cks = 0;

    if (session->kernel->attr.db_block_checksum == CKS_FULL) {
        page_calc_checksum(page, DEFAULT_PAGE_SIZE(session));
    }
    CM_RESTORE_STACK(session->stack);
    return OG_SUCCESS;
}

#ifdef __cplusplus
}
#endif