/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Copyright (c) 2023 Infineon Technologies AG
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE
 */
#ifdef HAVE_CONFIG_H
#include "config.h" // for MAXLOGLEVEL
#endif
#include <inttypes.h> // for uint8_t, uint32_t, uint16_t, PRIu32
#include <stdbool.h>  // for bool, true, false
#include <stdio.h>    // for size_t, NULL
#include <string.h>   // for memcpy, memset

#include "tcti-common.h" // for TSS2_TCTI_COMMON_CONTEXT, tpm_head...
#include "tcti-i2c-helper.h"
#include "tss2_common.h"          // for TSS2_RC_SUCCESS, TSS2_RC, TSS2_TCT...
#include "tss2_tcti.h"            // for TSS2_TCTI_CONTEXT, TSS2_TCTI_INFO
#include "tss2_tcti_i2c_helper.h" // for TSS2_TCTI_I2C_HELPER_PLATFORM, Tss...
#include "util/tss2_endian.h"     // for LE_TO_HOST_32, LE_TO_HOST_16, HOST...

#define LOGMODULE tcti
#include "util/log.h" // for LOG_ERROR, LOG_DEBUG, return_if_error

#define TIMEOUT_B 2000 /* The default timeout value as specified in the TCG spec. */

/*
 * CRC-CCITT KERMIT with following parameters:
 *
 * Length                           : 16 bit
 * Poly                             : 0x1021
 * Init                             : 0x0000
 * RefIn                            : False
 * RefOut                           : False
 * XorOut                           : 0x0000
 * Output for ASCII "123456789"     : 0x2189
 */
static const uint16_t crc16_kermit_lookup[256] = {
    0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3,
    0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
    0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399,
    0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
    0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50,
    0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
    0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e,
    0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
    0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5,
    0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
    0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693,
    0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
    0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a,
    0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
    0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710,
    0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
    0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df,
    0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
    0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595,
    0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
    0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c,
    0x3de3, 0x2c6a, 0x1ef1, 0x0f78
};

static uint16_t
crc_ccitt(const uint8_t *buffer, size_t size) {
    size_t   i;
    uint16_t result = 0;

    for (i = 0; i < size; i++) {
        uint8_t j = buffer[i] ^ result;
        result = crc16_kermit_lookup[j] ^ (result >> 8);
    }

    return result;
}

/*
 * This function wraps the "up-cast" of the opaque TCTI context type to the
 * type for the device TCTI context. The only safe-guard we have to ensure
 * this operation is possible is the magic number for the device TCTI context.
 * If passed a NULL context, or the magic number check fails, this function
 * will return NULL.
 */
TSS2_TCTI_I2C_HELPER_CONTEXT *
tcti_i2c_helper_context_cast(TSS2_TCTI_CONTEXT *tcti_ctx) {
    if (tcti_ctx != NULL && TSS2_TCTI_MAGIC(tcti_ctx) == TCTI_I2C_HELPER_MAGIC) {
        return (TSS2_TCTI_I2C_HELPER_CONTEXT *)tcti_ctx;
    }
    return NULL;
}

/*
 * This function down-casts the device TCTI context to the common context
 * defined in the tcti-common module.
 */
TSS2_TCTI_COMMON_CONTEXT *
tcti_i2c_helper_down_cast(TSS2_TCTI_I2C_HELPER_CONTEXT *tcti_i2c_helper) {
    if (tcti_i2c_helper == NULL) {
        return NULL;
    }
    return &tcti_i2c_helper->common;
}

static inline TSS2_RC
i2c_tpm_helper_delay_us(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx, int microseconds) {
    /* Sleep a specified amount of microseconds */
    if (ctx->platform.sleep_us == NULL) {
        return ctx->platform.sleep_ms(ctx->platform.user_data,
                                      (microseconds < 1000) ? 1 : (microseconds / 1000));
    } else {
        return ctx->platform.sleep_us(ctx->platform.user_data, microseconds);
    }
}

static inline TSS2_RC
i2c_tpm_helper_delay_ms(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx, int milliseconds) {
    /* Sleep a specified amount of milliseconds */
    if (ctx->platform.sleep_ms == NULL) {
        return ctx->platform.sleep_us(ctx->platform.user_data, milliseconds * 1000);
    } else {
        return ctx->platform.sleep_ms(ctx->platform.user_data, milliseconds);
    }
}

static inline TSS2_RC
i2c_tpm_helper_start_timeout(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx, int milliseconds) {
    /* Start a timeout timer with the specified amount of milliseconds */
    return ctx->platform.start_timeout(ctx->platform.user_data, milliseconds);
}

static inline TSS2_RC
i2c_tpm_helper_timeout_expired(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx, bool *result) {
    /* Check if the last started tiemout expired */
    return ctx->platform.timeout_expired(ctx->platform.user_data, result);
}

static inline TSS2_RC
i2c_tpm_helper_i2c_write(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx,
                         uint8_t                       reg_addr,
                         const void                   *data,
                         size_t                        cnt) {
    TSS2_RC rc;
    int     i = TCTI_I2C_HELPER_RETRY;

    do {
        /* Perform I2C write with cnt bytes */
        rc = ctx->platform.i2c_write(ctx->platform.user_data, reg_addr, data, cnt);
        if (rc == TSS2_RC_SUCCESS) {
            if (ctx->guard_time_write) {
                i2c_tpm_helper_delay_us(ctx, ctx->guard_time);
            }
            break;
        }

        i2c_tpm_helper_delay_us(ctx, TCTI_I2C_HELPER_DEFAULT_GUARD_TIME_US);
    } while (--i);

    return rc;
}

static inline TSS2_RC
i2c_tpm_helper_i2c_read(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx,
                        uint8_t                       reg_addr,
                        void                         *data,
                        size_t                        cnt) {
    TSS2_RC rc;
    int     i = TCTI_I2C_HELPER_RETRY;

    do {
        /* Perform I2C read with cnt bytes */
        rc = ctx->platform.i2c_read(ctx->platform.user_data, reg_addr, data, cnt);
        if (rc == TSS2_RC_SUCCESS) {
            if (ctx->guard_time_read) {
                i2c_tpm_helper_delay_us(ctx, ctx->guard_time);
            }
            break;
        }

        i2c_tpm_helper_delay_us(ctx, TCTI_I2C_HELPER_DEFAULT_GUARD_TIME_US);
    } while (--i);

    return rc;
}

static inline void
i2c_tpm_helper_platform_finalize(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx) {
    /* Free user_data and resources inside */
    if (ctx->platform.finalize)
        ctx->platform.finalize(ctx->platform.user_data);
}

static TSS2_RC
check_platform_conf(TSS2_TCTI_I2C_HELPER_PLATFORM *platform_conf) {

    bool required_set = (platform_conf->sleep_ms || platform_conf->sleep_us)
                        && platform_conf->i2c_write && platform_conf->i2c_read
                        && platform_conf->start_timeout && platform_conf->timeout_expired;
    if (!required_set) {
        LOG_ERROR("Expected sleep_us/sleep_ms, i2c_write, i2c_read, start_timeout and "
                  "timeout_expired to be set.");
        return TSS2_TCTI_RC_BAD_VALUE;
    }

    return TSS2_RC_SUCCESS;
}

static void
i2c_tpm_helper_log_register_access(enum TCTI_I2C_HELPER_REGISTER_ACCESS_TYPE access,
                                   uint8_t                                   reg_addr,
                                   const void                               *buffer,
                                   size_t                                    cnt,
                                   char                                     *err) {
#if MAXLOGLEVEL == LOGL_NONE
    (void)access;
    (void)reg_addr;
    (void)buffer;
    (void)cnt;
    (void)err;
#else
    /* Print register access debug information */
    char *access_str = (access == TCTI_I2C_HELPER_REGISTER_READ) ? "READ" : "WRITE";

    if (err != NULL) {
        LOG_ERROR("%s register %#02x (%zu bytes) %s", access_str, reg_addr, cnt, err);
    } else {
#if MAXLOGLEVEL < LOGL_TRACE
        (void)buffer;
#else
        LOGBLOB_TRACE(buffer, cnt, "%s register %#02x (%zu bytes)", access_str, reg_addr, cnt);
#endif
    }
#endif
}

static TSS2_RC
i2c_tpm_sanity_check_read(uint8_t reg, uint8_t *buffer, size_t cnt) {
    uint32_t zero_mask;
    uint32_t value;

    switch (cnt) {
    case sizeof(uint8_t):
        value = buffer[0];
        break;
    case sizeof(uint16_t):
        value = LE_TO_HOST_16(*((uint16_t *)buffer));
        break;
    case sizeof(uint32_t):
        value = LE_TO_HOST_32(*((uint32_t *)buffer));
        break;
    default:
        return TSS2_RC_SUCCESS;
    }

    switch (reg) {
    case TCTI_I2C_HELPER_TPM_ACCESS_REG:
        zero_mask = TCTI_I2C_HELPER_TPM_ACCESS_ZERO;
        break;
    case TCTI_I2C_HELPER_TPM_STS_REG:
        zero_mask = TCTI_I2C_HELPER_TPM_STS_ZERO;
        break;
    case TCTI_I2C_HELPER_TPM_INTERFACE_CAPABILITY_REG:
        zero_mask = TCTI_I2C_HELPER_TPM_INTERFACE_CAPABILITY_ZERO;
        break;
    default:
        return TSS2_RC_SUCCESS;
    }

    if (value & zero_mask) {
        LOG_ERROR("TPM I2C read of register 0x%02x failed sanity check", reg);
        return TSS2_TCTI_RC_IO_ERROR;
    }

    return TSS2_RC_SUCCESS;
}

static TSS2_RC
i2c_tpm_helper_read_reg(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx,
                        uint8_t                       reg_addr,
                        void                         *buffer,
                        size_t                        cnt) {
    TSS2_RC                                   rc;
    enum TCTI_I2C_HELPER_REGISTER_ACCESS_TYPE access = TCTI_I2C_HELPER_REGISTER_READ;

    /* Read register */
    rc = i2c_tpm_helper_i2c_read(ctx, reg_addr, buffer, cnt);
    if (rc != TSS2_RC_SUCCESS) {
        i2c_tpm_helper_log_register_access(access, reg_addr, NULL, cnt, "failed in transfer");
        return TSS2_TCTI_RC_IO_ERROR;
    }

    /* Sanity check, check that the 0 bits of the TPM_STS register are indeed 0s*/
    rc = i2c_tpm_sanity_check_read(reg_addr, buffer, cnt);
    if (rc != TSS2_RC_SUCCESS) {
        return rc;
    }

    /* Print debug information and return success */
    i2c_tpm_helper_log_register_access(access, reg_addr, buffer, cnt, NULL);
    return TSS2_RC_SUCCESS;
}

static TSS2_RC
i2c_tpm_helper_write_reg(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx,
                         uint8_t                       reg_addr,
                         const void                   *buffer,
                         size_t                        cnt) {
    TSS2_RC                                   rc;
    enum TCTI_I2C_HELPER_REGISTER_ACCESS_TYPE access = TCTI_I2C_HELPER_REGISTER_WRITE;

    /* Write register */
    rc = i2c_tpm_helper_i2c_write(ctx, reg_addr, buffer, cnt);
    if (rc != TSS2_RC_SUCCESS) {
        i2c_tpm_helper_log_register_access(access, reg_addr, buffer, cnt, "failed in transfer");
        return TSS2_TCTI_RC_IO_ERROR;
    }

    /* Print debug information and return success */
    i2c_tpm_helper_log_register_access(access, reg_addr, buffer, cnt, NULL);
    return TSS2_RC_SUCCESS;
}

static uint8_t
i2c_tpm_helper_read_access_reg(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx) {
    uint8_t access = 0;
    i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_ACCESS_REG, &access, sizeof(access));
    return access;
}

static void
i2c_tpm_helper_write_access_reg(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx, uint8_t access_bit) {
    /* Writes to access register can set only 1 bit at a time */
    if (access_bit & (access_bit - 1)) {
        LOG_ERROR("Writes to access register can set only 1 bit at a time.");
    } else {
        i2c_tpm_helper_write_reg(ctx, TCTI_I2C_HELPER_TPM_ACCESS_REG, &access_bit,
                                 sizeof(access_bit));
    }
}

static uint32_t
i2c_tpm_helper_read_sts_reg(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx) {
    uint32_t status = 0;
    i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_STS_REG, &status, sizeof(status));
    return LE_TO_HOST_32(status);
}

static void
i2c_tpm_helper_write_sts_reg(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx, uint32_t status) {
    status = HOST_TO_LE_32(status);
    i2c_tpm_helper_write_reg(ctx, TCTI_I2C_HELPER_TPM_STS_REG, &status, sizeof(status));
}

static uint32_t
i2c_tpm_helper_get_burst_count(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx) {
    uint32_t status = i2c_tpm_helper_read_sts_reg(ctx);
    return (status & TCTI_I2C_HELPER_TPM_STS_BURST_COUNT_MASK)
           >> TCTI_I2C_HELPER_TPM_STS_BURST_COUNT_SHIFT;
}

static inline size_t
i2c_tpm_helper_size_t_min(size_t a, size_t b) {
    if (a < b) {
        return a;
    }
    return b;
}

static inline uint32_t
i2c_tpm_helper_read_be32(const void *src) {
    const uint8_t *s = src;
    return (((uint32_t)s[0]) << 24) | (((uint32_t)s[1]) << 16) | (((uint32_t)s[2]) << 8)
           | (((uint32_t)s[3]) << 0);
}

static TSS2_RC
i2c_tpm_helper_claim_locality(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx) {
    uint8_t access;
    access = i2c_tpm_helper_read_access_reg(ctx);

    /* Check if locality 0 is active */
    if (access & TCTI_I2C_HELPER_TPM_ACCESS_ACTIVE_LOCALITY) {
        LOG_DEBUG("Locality 0 is already active, status: %#x", access);
        return TSS2_RC_SUCCESS;
    }

    /* Request locality 0 */
    i2c_tpm_helper_write_access_reg(ctx, TCTI_I2C_HELPER_TPM_ACCESS_REQUEST_USE);
    access = i2c_tpm_helper_read_access_reg(ctx);
    if (access & (TCTI_I2C_HELPER_TPM_ACCESS_VALID | TCTI_I2C_HELPER_TPM_ACCESS_ACTIVE_LOCALITY)) {
        LOG_DEBUG("Claimed locality 0");
        return TSS2_RC_SUCCESS;
    }

    LOG_ERROR("Failed to claim locality 0, status: %#x", access);
    return TSS2_TCTI_RC_IO_ERROR;
}

static TSS2_RC
i2c_tpm_helper_init_guard_time(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx) {
    TSS2_RC  rc;
    uint32_t i2c_caps;

    ctx->guard_time_read = true;
    ctx->guard_time_write = true;
    ctx->guard_time = TCTI_I2C_HELPER_DEFAULT_GUARD_TIME_US;

    rc = i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_INTERFACE_CAPABILITY_REG, &i2c_caps,
                                 sizeof(i2c_caps));
    if (rc != TSS2_RC_SUCCESS) {
        LOG_ERROR("Failed to read TPM_I2C_INTERFACE_CAPABILITY register");
        return rc;
    }

    i2c_caps = LE_TO_HOST_32(i2c_caps);

    ctx->guard_time_read = (i2c_caps & TCTI_I2C_HELPER_TPM_GUARD_TIME_RR_MASK)
                           || (i2c_caps & TCTI_I2C_HELPER_TPM_GUARD_TIME_RW_MASK);
    ctx->guard_time_write = (i2c_caps & TCTI_I2C_HELPER_TPM_GUARD_TIME_WR_MASK)
                            || (i2c_caps & TCTI_I2C_HELPER_TPM_GUARD_TIME_WW_MASK);
    ctx->guard_time
        = (i2c_caps & TCTI_I2C_HELPER_TPM_GUARD_TIME_MASK) >> TCTI_I2C_HELPER_TPM_GUARD_TIME_SHIFT;

    return TSS2_RC_SUCCESS;
}

static TSS2_RC
i2c_tpm_helper_enable_crc(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx) {
    TSS2_RC rc;
    uint8_t crc_enable;

    rc = i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_DATA_CSUM_ENABLE_REG, &crc_enable,
                                 sizeof(crc_enable));
    if (rc != TSS2_RC_SUCCESS) {
        return rc;
    }

    if (crc_enable == 1) {
        return TSS2_RC_SUCCESS;
    }

    crc_enable = 1;
    return i2c_tpm_helper_write_reg(ctx, TCTI_I2C_HELPER_TPM_DATA_CSUM_ENABLE_REG, &crc_enable,
                                    sizeof(crc_enable));
}

static TSS2_RC
i2c_tpm_helper_wait_for_status(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx,
                               uint32_t                      status_mask,
                               uint32_t                      status_expected,
                               int32_t                       timeout) {
    TSS2_RC  rc;
    uint32_t status;
    bool     blocking = (timeout == TSS2_TCTI_TIMEOUT_BLOCK);
    if (!blocking) {
        rc = i2c_tpm_helper_start_timeout(ctx, timeout);
        return_if_error(rc, "i2c_tpm_helper_start_timeout");
    }

    /* Wait for the expected status with or without timeout */
    bool is_timeout_expired = false;
    do {
        status = i2c_tpm_helper_read_sts_reg(ctx);
        /* Return success on expected status */
        if ((status & status_mask) == status_expected) {
            return TSS2_RC_SUCCESS;
        }
        /* Delay next poll by 8ms to avoid spamming the TPM */
        rc = i2c_tpm_helper_delay_ms(ctx, 8);
        return_if_error(rc, "i2c_tpm_helper_delay_ms");

        rc = i2c_tpm_helper_timeout_expired(ctx, &is_timeout_expired);
        return_if_error(rc, "i2c_tpm_helper_timeout_expired");
    } while (blocking || !is_timeout_expired);

    /* Timed out */
    return TSS2_TCTI_RC_TRY_AGAIN;
}

static TSS2_RC
i2c_tpm_helper_verify_crc(TSS2_TCTI_I2C_HELPER_CONTEXT *ctx, const uint8_t *buffer, size_t size) {
    TSS2_RC  rc;
    uint16_t crc_tpm = 0;
    uint16_t crc_host = 0;

    rc = i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_DATA_CSUM_REG, &crc_tpm, sizeof(crc_tpm));
    if (rc != TSS2_RC_SUCCESS) {
        return rc;
    }

    crc_tpm = LE_TO_HOST_16(crc_tpm);
    /* Reflect crc result, regardless of host endianness */
    crc_tpm = ((crc_tpm >> 8) & 0xFFu) | ((crc_tpm << 8) & 0xFF00u);
    crc_host = crc_ccitt(buffer, size);

    if (crc_tpm == crc_host) {
        return TSS2_RC_SUCCESS;
    }

    return TSS2_TCTI_RC_IO_ERROR;
}

static void
i2c_tpm_helper_fifo_transfer(TSS2_TCTI_I2C_HELPER_CONTEXT                *ctx,
                             uint8_t                                     *transfer_buffer,
                             size_t                                       transfer_size,
                             enum TCTI_I2C_HELPER_FIFO_TRANSFER_DIRECTION direction) {
    size_t transaction_size;
    size_t burst_count;
    size_t handled_so_far = 0;

    do {
        do {
            /* Can be zero when TPM is busy */
            burst_count = i2c_tpm_helper_get_burst_count(ctx);
        } while (!burst_count);

        transaction_size = transfer_size - handled_so_far;
        transaction_size = i2c_tpm_helper_size_t_min(transaction_size, burst_count);

        if (direction == TCTI_I2C_HELPER_FIFO_RECEIVE) {
            i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_DATA_FIFO_REG,
                                    (void *)(transfer_buffer + handled_so_far), transaction_size);
        } else {
            i2c_tpm_helper_write_reg(ctx, TCTI_I2C_HELPER_TPM_DATA_FIFO_REG,
                                     (const void *)(transfer_buffer + handled_so_far),
                                     transaction_size);
        }

        handled_so_far += transaction_size;

    } while (handled_so_far != transfer_size);
}

TSS2_RC
tcti_i2c_helper_transmit(TSS2_TCTI_CONTEXT *tcti_ctx, size_t size, const uint8_t *cmd_buf) {
    TSS2_RC                       rc;
    TSS2_TCTI_I2C_HELPER_CONTEXT *tcti_i2c_helper = tcti_i2c_helper_context_cast(tcti_ctx);
    TSS2_TCTI_COMMON_CONTEXT     *tcti_common = tcti_i2c_helper_down_cast(tcti_i2c_helper);
    tpm_header_t                  header;

    if (tcti_i2c_helper == NULL) {
        return TSS2_TCTI_RC_BAD_CONTEXT;
    }
    TSS2_TCTI_I2C_HELPER_CONTEXT *ctx = tcti_i2c_helper;

    rc = tcti_common_transmit_checks(tcti_common, cmd_buf, TCTI_I2C_HELPER_MAGIC);
    if (rc != TSS2_RC_SUCCESS) {
        return rc;
    }
    rc = header_unmarshal(cmd_buf, &header);
    if (rc != TSS2_RC_SUCCESS) {
        return rc;
    }
    if (header.size != size) {
        LOG_ERROR("Buffer size parameter: %zu, and TPM2 command header size "
                  "field: %" PRIu32 " disagree.",
                  size, header.size);
        return TSS2_TCTI_RC_BAD_VALUE;
    }

    LOGBLOB_DEBUG(cmd_buf, size, "Sending command with TPM_CC %#" PRIx32 " and size %" PRIu32,
                  header.code, header.size);

    /* Tell TPM to expect command */
    i2c_tpm_helper_write_sts_reg(ctx, TCTI_I2C_HELPER_TPM_STS_COMMAND_READY);

    /* Wait until ready bit is set by TPM device */
    uint32_t expected_status_bits = TCTI_I2C_HELPER_TPM_STS_COMMAND_READY;
    rc = i2c_tpm_helper_wait_for_status(ctx, expected_status_bits, expected_status_bits, TIMEOUT_B);
    if (rc != TSS2_RC_SUCCESS) {
        LOG_ERROR("Failed waiting for TPM to become ready");
        return rc;
    }

    /* Send command */
    i2c_tpm_helper_fifo_transfer(ctx, (void *)cmd_buf, size, TCTI_I2C_HELPER_FIFO_TRANSMIT);

    /* Verify CRC */
    rc = i2c_tpm_helper_verify_crc(ctx, cmd_buf, size);
    if (rc != TSS2_RC_SUCCESS) {
        LOG_ERROR("CRC mismatch for command");
        return rc;
    }

    /* Tell TPM to start processing the command */
    i2c_tpm_helper_write_sts_reg(ctx, TCTI_I2C_HELPER_TPM_STS_GO);

    tcti_common->state = TCTI_STATE_RECEIVE;
    return TSS2_RC_SUCCESS;
}

TSS2_RC
tcti_i2c_helper_receive(TSS2_TCTI_CONTEXT *tcti_context,
                        size_t            *response_size,
                        unsigned char     *response_buffer,
                        int32_t            timeout) {
    TSS2_RC                       rc;
    TSS2_TCTI_I2C_HELPER_CONTEXT *tcti_i2c_helper = tcti_i2c_helper_context_cast(tcti_context);
    TSS2_TCTI_COMMON_CONTEXT     *tcti_common = tcti_i2c_helper_down_cast(tcti_i2c_helper);

    if (tcti_i2c_helper == NULL) {
        return TSS2_TCTI_RC_BAD_CONTEXT;
    }

    rc = tcti_common_receive_checks(tcti_common, response_size, TCTI_I2C_HELPER_MAGIC);
    if (rc != TSS2_RC_SUCCESS) {
        return rc;
    }

    /* Use ctx as a shorthand for tcti_i2c_helper */
    TSS2_TCTI_I2C_HELPER_CONTEXT *ctx = tcti_i2c_helper;

    /* Expected status bits for valid status and data availabe */
    uint32_t expected_status_bits
        = TCTI_I2C_HELPER_TPM_STS_VALID | TCTI_I2C_HELPER_TPM_STS_DATA_AVAIL;

    /* Check if we already have received the header */
    if (tcti_common->header.size == 0) {
        /* Wait for response to be ready */
        rc = i2c_tpm_helper_wait_for_status(ctx, expected_status_bits, expected_status_bits,
                                            timeout);
        if (rc != TSS2_RC_SUCCESS) {
            LOG_ERROR("Failed waiting for status");
            /* Return rc from wait_for_status(). May be TRY_AGAIN after timeout. */
            return rc;
        }

        /* Read only response header into context header buffer */
        rc = i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_DATA_FIFO_REG, ctx->header,
                                     TCTI_I2C_HELPER_RESP_HEADER_SIZE);
        if (rc != TSS2_RC_SUCCESS) {
            LOG_ERROR("Failed reading response header");
            return TSS2_TCTI_RC_IO_ERROR;
        }

        /* Find out the total payload size, skipping the two byte tag and update tcti_common */
        tcti_common->header.size = i2c_tpm_helper_read_be32(ctx->header + 2);
        LOG_TRACE("Read response size from response header: %" PRIu32 " bytes",
                  tcti_common->header.size);
    }

    /* Check if response size is requested */
    if (response_buffer == NULL) {
        *response_size = tcti_common->header.size;
        LOG_TRACE("Caller requested response size. Returning size of %zu bytes", *response_size);
        return TSS2_RC_SUCCESS;
    }

    /* Check if response fits in buffer and update response size */
    if (tcti_common->header.size > *response_size) {
        LOG_ERROR("TPM response too long (%" PRIu32 " bytes)", tcti_common->header.size);
        return TSS2_TCTI_RC_INSUFFICIENT_BUFFER;
    }
    *response_size = tcti_common->header.size;

    /* Receive the TPM response */
    LOG_TRACE("Reading response of size %" PRIu32, tcti_common->header.size);

    /* Copy already received header into response buffer */
    memcpy(response_buffer, ctx->header, TCTI_I2C_HELPER_RESP_HEADER_SIZE);

    /* Read all but the last byte in the FIFO */
    size_t bytes_to_go = tcti_common->header.size - 1 - TCTI_I2C_HELPER_RESP_HEADER_SIZE;
    i2c_tpm_helper_fifo_transfer(ctx, response_buffer + TCTI_I2C_HELPER_RESP_HEADER_SIZE,
                                 bytes_to_go, TCTI_I2C_HELPER_FIFO_RECEIVE);

    /* Verify that there is still data to read */
    uint32_t status = i2c_tpm_helper_read_sts_reg(ctx);
    if ((status & expected_status_bits) != expected_status_bits) {
        LOG_ERROR("Unexpected intermediate status %#" PRIx32, status);
        return TSS2_TCTI_RC_IO_ERROR;
    }

    /* Read the last byte */
    rc = i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_DATA_FIFO_REG,
                                 response_buffer + tcti_common->header.size - 1, 1);
    if (rc != TSS2_RC_SUCCESS) {
        return TSS2_TCTI_RC_IO_ERROR;
    }

    /* Verify that there is no more data available */
    status = i2c_tpm_helper_read_sts_reg(ctx);
    if ((status & expected_status_bits) != TCTI_I2C_HELPER_TPM_STS_VALID) {
        LOG_ERROR("Unexpected final status %#" PRIx32, status);
        return TSS2_TCTI_RC_IO_ERROR;
    }

    /* Verify CRC */
    rc = i2c_tpm_helper_verify_crc(ctx, response_buffer, *response_size);
    if (rc != TSS2_RC_SUCCESS) {
        LOG_ERROR("CRC mismatch for response");
        return rc;
    }

    LOGBLOB_DEBUG(response_buffer, tcti_common->header.size, "Response buffer received:");

    /* Set the TPM back to idle state */
    i2c_tpm_helper_write_sts_reg(ctx, TCTI_I2C_HELPER_TPM_STS_COMMAND_READY);

    tcti_common->header.size = 0;
    tcti_common->state = TCTI_STATE_TRANSMIT;

    return TSS2_RC_SUCCESS;
}

void
tcti_i2c_helper_finalize(TSS2_TCTI_CONTEXT *tcti_context) {
    TSS2_TCTI_I2C_HELPER_CONTEXT *tcti_i2c_helper = tcti_i2c_helper_context_cast(tcti_context);
    TSS2_TCTI_COMMON_CONTEXT     *tcti_common = tcti_i2c_helper_down_cast(tcti_i2c_helper);

    if (tcti_i2c_helper == NULL) {
        return;
    }
    tcti_common->state = TCTI_STATE_FINAL;

    /* Free platform struct user data and resources inside */
    i2c_tpm_helper_platform_finalize(tcti_i2c_helper);
}

TSS2_RC
tcti_i2c_helper_cancel(TSS2_TCTI_CONTEXT *tcti_context) {
    (void)(tcti_context);
    return TSS2_TCTI_RC_NOT_IMPLEMENTED;
}

TSS2_RC
tcti_i2c_helper_get_poll_handles(TSS2_TCTI_CONTEXT     *tcti_context,
                                 TSS2_TCTI_POLL_HANDLE *handles,
                                 size_t                *num_handles) {
    (void)(tcti_context);
    (void)(handles);
    (void)(num_handles);
    return TSS2_TCTI_RC_NOT_IMPLEMENTED;
}

TSS2_RC
tcti_i2c_helper_set_locality(TSS2_TCTI_CONTEXT *tcti_context, uint8_t locality) {
    (void)(tcti_context);
    (void)(locality);
    return TSS2_TCTI_RC_NOT_IMPLEMENTED;
}

TSS2_RC
Tss2_Tcti_I2c_Helper_Init(TSS2_TCTI_CONTEXT             *tcti_context,
                          size_t                        *size,
                          TSS2_TCTI_I2C_HELPER_PLATFORM *platform_conf) {
    TSS2_RC                       rc;
    TSS2_TCTI_I2C_HELPER_CONTEXT *tcti_i2c_helper;
    TSS2_TCTI_COMMON_CONTEXT     *tcti_common;

    if (!size) {
        return TSS2_TCTI_RC_BAD_VALUE;
    }

    /* Check if context size is requested */
    if (tcti_context == NULL) {
        *size = sizeof(TSS2_TCTI_I2C_HELPER_CONTEXT);
        return TSS2_RC_SUCCESS;
    }

    if (!platform_conf) {
        return TSS2_TCTI_RC_BAD_VALUE;
    }

    if (*size < sizeof(TSS2_TCTI_I2C_HELPER_CONTEXT)) {
        return TSS2_TCTI_RC_INSUFFICIENT_BUFFER;
    }

    /* Init TCTI context */
    TSS2_TCTI_MAGIC(tcti_context) = TCTI_I2C_HELPER_MAGIC;
    TSS2_TCTI_VERSION(tcti_context) = TCTI_VERSION;
    TSS2_TCTI_TRANSMIT(tcti_context) = tcti_i2c_helper_transmit;
    TSS2_TCTI_RECEIVE(tcti_context) = tcti_i2c_helper_receive;
    TSS2_TCTI_FINALIZE(tcti_context) = tcti_i2c_helper_finalize;
    TSS2_TCTI_CANCEL(tcti_context) = tcti_i2c_helper_cancel;
    TSS2_TCTI_GET_POLL_HANDLES(tcti_context) = tcti_i2c_helper_get_poll_handles;
    TSS2_TCTI_SET_LOCALITY(tcti_context) = tcti_i2c_helper_set_locality;
    TSS2_TCTI_MAKE_STICKY(tcti_context) = tcti_make_sticky_not_implemented;

    /* Init I2C TCTI context */
    tcti_i2c_helper = tcti_i2c_helper_context_cast(tcti_context);
    tcti_common = tcti_i2c_helper_down_cast(tcti_i2c_helper);
    tcti_common->state = TCTI_STATE_TRANSMIT;
    memset(&tcti_common->header, 0, sizeof(tcti_common->header));
    tcti_common->locality = 0;

    rc = check_platform_conf(platform_conf);
    return_if_error(rc, "platform_conf invalid");

    /* Copy platform struct into context */
    tcti_i2c_helper->platform = *platform_conf;

    /* Probe TPM */
    TSS2_TCTI_I2C_HELPER_CONTEXT *ctx = tcti_i2c_helper;
    LOG_DEBUG("Probing TPM...");
    uint32_t did_vid = 0;
    for (int retries = 100; retries > 0; retries--) {
        /* In case of failed read div_vid is set to zero */
        i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_DID_VID_REG, &did_vid, sizeof(did_vid));
        if (did_vid != 0) {
            did_vid = LE_TO_HOST_32(did_vid);
            break;
        }
        /* TPM might be resetting, let's retry in a bit */
        rc = i2c_tpm_helper_delay_ms(ctx, 10);
        return_if_error(rc, "i2c_tpm_helper_delay_ms");
    }
    if (did_vid == 0) {
        LOG_ERROR("Probing TPM failed");
        return TSS2_TCTI_RC_IO_ERROR;
    }
    LOG_DEBUG("Probing TPM successful");

    /* Init guard time */
    LOG_DEBUG("Initializing guard time");
    rc = i2c_tpm_helper_init_guard_time(ctx);
    if (rc != TSS2_RC_SUCCESS) {
        return TSS2_TCTI_RC_IO_ERROR;
    }

    /* Claim locality */
    LOG_DEBUG("Claiming TPM locality");
    rc = i2c_tpm_helper_claim_locality(ctx);
    if (rc != TSS2_RC_SUCCESS) {
        return TSS2_TCTI_RC_IO_ERROR;
    }

    /* Enable Data Checksum */
    LOG_DEBUG("Enable Data Checksum");
    rc = i2c_tpm_helper_enable_crc(ctx);
    if (rc != TSS2_RC_SUCCESS) {
        return TSS2_TCTI_RC_IO_ERROR;
    }

    /* Wait up to TIMEOUT_B for TPM to become ready */
    LOG_DEBUG("Waiting for TPM to become ready...");
    uint32_t expected_status_bits = TCTI_I2C_HELPER_TPM_STS_COMMAND_READY;
    rc = i2c_tpm_helper_wait_for_status(ctx, expected_status_bits, expected_status_bits, TIMEOUT_B);
    if (rc == TSS2_TCTI_RC_TRY_AGAIN) {
        /*
         * TPM did not auto transition into ready state,
         * write 1 to commandReady to start the transition.
         */
        i2c_tpm_helper_write_sts_reg(ctx, TCTI_I2C_HELPER_TPM_STS_COMMAND_READY);
        rc = i2c_tpm_helper_wait_for_status(ctx, expected_status_bits, expected_status_bits,
                                            TIMEOUT_B);
    }
    if (rc != TSS2_RC_SUCCESS) {
        LOG_ERROR("Failed waiting for TPM to become ready");
        return rc;
    }

    LOG_DEBUG("TPM is ready");

    /* Get rid */
    uint8_t rid = 0;
    i2c_tpm_helper_read_reg(ctx, TCTI_I2C_HELPER_TPM_RID_REG, &rid, sizeof(rid));

#if MAXLOGLEVEL >= LOGL_INFO
    /* Print device details */
    uint16_t vendor_id, device_id, revision;
    vendor_id = did_vid & 0xffff;
    device_id = did_vid >> 16;
    revision = rid;
    LOG_INFO("Connected to TPM with vid:did:rid of %4.4x:%4.4x:%2.2x", vendor_id, device_id,
             revision);
#endif

    return TSS2_RC_SUCCESS;
}

static const TSS2_TCTI_INFO tss2_tcti_i2c_helper_info = {
    .version = TCTI_VERSION,
    .name = "tcti-i2c-helper",
    .description = "Platform independent TCTI for communication with TPMs over I2C.",
    .config_help = "TSS2_TCTI_I2C_HELPER_PLATFORM struct containing platform methods. See "
                   "tss2_tcti_i2c_helper.h for more information.",

    /*
     * The Tss2_Tcti_I2c_Helper_Init method has a different signature than required by .init due too
     * our custom platform_conf parameter, so we can't expose it here and it has to be used
     * directly.
     */
    .init = NULL,
};

const TSS2_TCTI_INFO *
Tss2_Tcti_Info(void) {
    return &tss2_tcti_i2c_helper_info;
}
