/*****************************************************************************
 * Copyright Gemalto, unpublished work, created 2015. This computer program
 * includes Confidential, Proprietary Information and is a Trade Secret of
 * Gemalto. All use, disclosure, and/or reproduction is prohibited unless
 * authorised in writing by an officer of Gemalto. All Rights Reserved.
 *
 * Gemalto licenses this file to you under the secESE Gemalto License.
 * See NOTICE file for more information regarding copyright ownership.
 * A copy of secESE Gemalto License is available in LICENSE file included in
 * source code distribution of secESE Gemalto. You can ask a copy of the
 * License by contacting Gemalto (http://www.gemalto.com).
 ****************************************************************************/

/**
 * @file
 * $Author$
 * $Revision$
 * $Date$
 *
 * eSE Gemalto kernel driver transport.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include "iso7816_t1.h"
#include "transport.h"
#include "spi.h"
#include "secese.h"

#ifdef USE_QSEE
#include "qsee_timer.h"
#endif
#include "tz_log.h"

#ifdef USE_MOBICORE
#include "tlapi_secdrv.h"
#include "TlApi/TlApi.h"
#endif

#ifdef USE_QSEE
#define POLLING_INTERVAL 2
#define TIMEOUT_TIME 2000
#endif

#ifdef USE_MOBICORE
#define POLLING_INTERVAL 2000
#define TIMEOUT_TIME 2000000
#endif

#define NSEC_PER_SEC  1000000000L
#define NSEC_PER_MSEC 1000000L
#define NSEC_PER_MICROSEC 1000L

#define ESE_NAD 0x21

/* < 0 if t1 < t2,
 * > 0 if t1 > t2,
 *   0 if t1 == t2.
 */

struct timespec {
    long     tv_sec;        /* seconds */
    long     tv_nsec;       /* nanoseconds */
};

static struct timespec
getCurrentTime()
{
    struct timespec ts;
#ifdef USE_QSEE
    unsigned long long tempTime;
    tempTime = qsee_get_uptime();
    ts.tv_sec = (tempTime / 1000);
    ts.tv_nsec = (tempTime % 1000) * NSEC_PER_MSEC;
#endif
#ifdef USE_MOBICORE
    timestamp_t tempTime;
    tlApiGetSecureTimestamp(&tempTime);
    ts.tv_sec = (tempTime / 1000000);
    ts.tv_nsec = (tempTime % 1000000) * NSEC_PER_MICROSEC;
#endif
#ifdef USE_BLOWFISH
    TEE_Time time;
    TEE_GetSystemTime(&time);
    ts.tv_sec = time.seconds;
    ts.tv_nsec = time.millis * NSEC_PER_MSEC;
#endif

    return ts;
}

static int
ts_compare(const struct timespec *t1, const struct timespec *t2)
{
    if (t1->tv_sec < t2->tv_sec)
        return -1;
    else if (t1->tv_sec > t2->tv_sec)
        return 1;
    else
        return t1->tv_nsec - t2->tv_nsec;
}

static uint32_t
div_uint64_rem(uint64_t dividend, uint32_t divisor, uint64_t *remainder)
{
    uint32_t r    = 0;

    /* Compiler will optimize to modulo on capable platform */
    while (dividend >= divisor)
        dividend -= divisor, r++;

    *remainder = dividend;
    return r;
}

static struct timespec
ts_add_ns(const struct timespec ta, uint64_t ns)
{
    long sec = ta.tv_sec +
                 div_uint64_rem(ta.tv_nsec + ns, NSEC_PER_SEC, &ns);
    struct timespec ts = { sec, ns };

    return ts;
}

static int
crc_length(struct t1_state *t1)
{
    int n = 0;

    switch (t1->chk_algo) {
        case CHECKSUM_LRC:
            n = 1;
            break;

        case CHECKSUM_CRC:
            n = 2;
            break;
    }
    return n;
}

int
block_send(struct t1_state *t1, const void *block, size_t n)
{
    (void)t1;

    if (n < 4) {
        return ESE_INVALID_DATA;
    }

    return spi_write_gem(block, n);
}

int
block_recv(struct t1_state *t1, void *block, size_t n)
{
    uint8_t  c;
    uint8_t *s, i;
    int      len, max;
    uint64_t bwt;
    struct timespec ts, ts_timeout, current_ts;

    if (n < 4) {
        return ESE_INVALID_DATA;
    }
    s  = block;

    ts = getCurrentTime();
    bwt = t1->bwt * (t1->wtx ? t1->wtx : 1);
    t1->wtx = 1;

    ts_timeout = ts_add_ns(ts, bwt*NSEC_PER_MSEC);

    /* Pull every 2ms */
    i = 0;
    do {
//        LOGI("origin ts : %d, %ld", ts.tv_sec, ts.tv_nsec);
        /* Wait for 2ms */
        ts = ts_add_ns(ts,2*NSEC_PER_MSEC);

        do {
            current_ts = getCurrentTime();
//            LOGI("ts : %d, %ld", ts.tv_sec, ts.tv_nsec);
//            LOGI("current_ts : %d, %ld", current_ts.tv_sec, current_ts.tv_nsec);
//            LOGI("ts_compare : %d", ts_compare(&ts, &current_ts));
        } while (ts_compare(&ts, &current_ts) >= 0);

        len = spi_read_gem(&c, 1);
        if (len < 0)
            return len;

        if (ts_compare(&ts, &ts_timeout) >= 0)
            return ESE_TIME_OUT;
    } while (c != ESE_NAD);

    s[i++] = c;

    /* Minimal length is 3 + sizeof(checksum) */
    max = 2 + crc_length(t1);
    len = spi_read_gem(s + 1, max);
    if (len < 0) {
        return len;
    }
    i += len;

    /* verify that buffer is large enough. */
    max += s[2];
    if ((size_t)max > n) {
        return ESE_UNKNOWN_ERROR;
    }

    /* get block remaining if present */
    if (s[2] > 0x00 && s[2] <= 0xFF) {
        len = spi_read_gem(s + 4, s[2]);
        if (len < 0) {
            return len;
        }
    }

    return max+1;
}
