/**
 * \file
 * \brief Date handling with regard to certificates.
 *
 * \copyright (c) 2015-2020 Microchip Technology Inc. and its subsidiaries.
 *
 * \page License
 *
 * Subject to your compliance with these terms, you may use Microchip software
 * and any derivatives exclusively with Microchip products. It is your
 * responsibility to comply with third party license terms applicable to your
 * use of third party software (including open source software) that may
 * accompany Microchip software.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
 * EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
 * PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT,
 * SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE
 * OF ANY KIND WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF
 * MICROCHIP HAS BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE
 * FORESEEABLE. TO THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL
 * LIABILITY ON ALL CLAIMS IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED
 * THE AMOUNT OF FEES, IF ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR
 * THIS SOFTWARE.
 */

#include <string.h>
#include "atcacert_date.h"


const size_t ATCACERT_DATE_FORMAT_SIZES[ATCACERT_DATE_FORMAT_SIZES_COUNT] = {
    DATEFMT_ISO8601_SEP_SIZE,
    DATEFMT_RFC5280_UTC_SIZE,
    DATEFMT_POSIX_UINT32_BE_SIZE,
    DATEFMT_POSIX_UINT32_LE_SIZE,
    DATEFMT_RFC5280_GEN_SIZE
};

int atcacert_date_enc(atcacert_date_format_t   format,
                      const atcacert_tm_utc_t* timestamp,
                      uint8_t*                 formatted_date,
                      size_t*                  formatted_date_size)
{
    if (timestamp == NULL || formatted_date_size == NULL || format >= sizeof(ATCACERT_DATE_FORMAT_SIZES) / sizeof(ATCACERT_DATE_FORMAT_SIZES[0]))
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    if (formatted_date != NULL && *formatted_date_size < ATCACERT_DATE_FORMAT_SIZES[format])
    {
        *formatted_date_size = ATCACERT_DATE_FORMAT_SIZES[format];
        return ATCACERT_E_BUFFER_TOO_SMALL;
    }
    *formatted_date_size = ATCACERT_DATE_FORMAT_SIZES[format];
    if (formatted_date == NULL)
    {
        return ATCACERT_E_SUCCESS;  // Caller just wanted

    }
    switch (format)
    {
    case DATEFMT_ISO8601_SEP:     return atcacert_date_enc_iso8601_sep(timestamp, formatted_date);
    case DATEFMT_RFC5280_UTC:     return atcacert_date_enc_rfc5280_utc(timestamp, formatted_date);
    case DATEFMT_POSIX_UINT32_BE: return atcacert_date_enc_posix_uint32_be(timestamp, formatted_date);
    case DATEFMT_POSIX_UINT32_LE: return atcacert_date_enc_posix_uint32_le(timestamp, formatted_date);
    case DATEFMT_RFC5280_GEN:     return atcacert_date_enc_rfc5280_gen(timestamp, formatted_date);
    default: break;
    }

    return ATCACERT_E_BAD_PARAMS;
}

int atcacert_date_dec(atcacert_date_format_t format,
                      const uint8_t*         formatted_date,
                      size_t                 formatted_date_size,
                      atcacert_tm_utc_t*     timestamp)
{
    if (formatted_date == NULL || timestamp == NULL || format >= sizeof(ATCACERT_DATE_FORMAT_SIZES) / sizeof(ATCACERT_DATE_FORMAT_SIZES[0]))
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    if (formatted_date_size < ATCACERT_DATE_FORMAT_SIZES[format])
    {
        return ATCACERT_E_DECODING_ERROR;  // Not enough data to parse this date format

    }
    switch (format)
    {
    case DATEFMT_ISO8601_SEP:     return atcacert_date_dec_iso8601_sep(formatted_date, timestamp);
    case DATEFMT_RFC5280_UTC:     return atcacert_date_dec_rfc5280_utc(formatted_date, timestamp);
    case DATEFMT_POSIX_UINT32_BE: return atcacert_date_dec_posix_uint32_be(formatted_date, timestamp);
    case DATEFMT_POSIX_UINT32_LE: return atcacert_date_dec_posix_uint32_le(formatted_date, timestamp);
    case DATEFMT_RFC5280_GEN:     return atcacert_date_dec_rfc5280_gen(formatted_date, timestamp);
    default: break;
    }

    return ATCACERT_E_BAD_PARAMS;
}

int atcacert_date_get_max_date(atcacert_date_format_t format, atcacert_tm_utc_t* timestamp)
{

    if (timestamp == NULL || format >= sizeof(ATCACERT_DATE_FORMAT_SIZES) / sizeof(ATCACERT_DATE_FORMAT_SIZES[0]))
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    switch (format)
    {
    case DATEFMT_ISO8601_SEP:
        timestamp->tm_year = 9999 - 1900;
        timestamp->tm_mon  = 12 - 1;
        timestamp->tm_mday = 31;
        timestamp->tm_hour = 23;
        timestamp->tm_min  = 59;
        timestamp->tm_sec  = 59;
        break;

    case DATEFMT_RFC5280_UTC:
        timestamp->tm_year = 2049 - 1900;
        timestamp->tm_mon  = 12 - 1;
        timestamp->tm_mday = 31;
        timestamp->tm_hour = 23;
        timestamp->tm_min  = 59;
        timestamp->tm_sec  = 59;
        break;

    case DATEFMT_POSIX_UINT32_BE:
        timestamp->tm_year = 2106 - 1900;
        timestamp->tm_mon  = 2 - 1;
        timestamp->tm_mday = 7;
        timestamp->tm_hour = 6;
        timestamp->tm_min  = 28;
        timestamp->tm_sec  = 15;
        break;

    case DATEFMT_POSIX_UINT32_LE:
        timestamp->tm_year = 2106 - 1900;
        timestamp->tm_mon  = 2 - 1;
        timestamp->tm_mday = 7;
        timestamp->tm_hour = 6;
        timestamp->tm_min  = 28;
        timestamp->tm_sec  = 15;
        break;

    case DATEFMT_RFC5280_GEN:
        timestamp->tm_year = 9999 - 1900;
        timestamp->tm_mon  = 12 - 1;
        timestamp->tm_mday = 31;
        timestamp->tm_hour = 23;
        timestamp->tm_min  = 59;
        timestamp->tm_sec  = 59;
        break;

    default: return ATCACERT_E_BAD_PARAMS;
    }

    return ATCACERT_E_SUCCESS;
}

/**
 * \brief Convert an unsigned integer to a zero padded string with no terminating null.
 */
static uint8_t* uint_to_str(int num, int width, uint8_t* str)
{
    uint8_t* ret = str + width;
    int i;

    // Pre-fill the string width with zeros
    for (i = 0; i < width; i++)
    {
        *(str++) = '0';
    }
    // Convert the number from right to left
    for (; num; num /= 10)
    {
        *(--str) = '0' + (num % 10);
    }

    return ret;
}

/**
 * \brief Convert a number string as a zero padded unsigned integer back into a number
 */
static const uint8_t* str_to_uint(const uint8_t* str, int width, uint32_t* num)
{
    const uint8_t* error_ret = str;
    const uint8_t* good_ret = str + width;
    uint32_t prev_num = 0;
    uint32_t digit_value = 1;
    int digit;

    str += width - 1;
    *num = 0;
    for (digit = 0; digit < width; digit++)
    {
        if (*str < '0' || *str > '9')
        {
            return error_ret;  // Character is not a digit
        }
        if (digit >= 10)
        {
            if (*str != '0')
            {
                return error_ret;  // Number is larger than the output can handle
            }
            continue;
        }
        if (digit == 9 && *str > '4')
        {
            return error_ret;  // Number is larger than the output can handle

        }
        *num += digit_value * (*str - '0');
        if (*num < prev_num)
        {
            return error_ret;  // Number rolled over, it is larger than the output can handle

        }
        digit_value *= 10;
        prev_num = *num;
        str--;
    }

    return good_ret;
}

/**
 * \brief Convert a number string as a zero padded unsigned integer back into a number constrained
 *        to an integer's size.
 */
static const uint8_t* str_to_int(const uint8_t* str, int width, int* num)
{
    uint32_t unum = 0;
    const uint8_t* ret = str_to_uint(str, width, &unum);

    if (ret != str && unum > 2147483647UL)
    {
        ret = str;  // Number exceeds int32's range
    }
    *num = (int)unum;
    return ret;
}

int atcacert_date_enc_iso8601_sep(const atcacert_tm_utc_t* timestamp,
                                  uint8_t                  formatted_date[DATEFMT_ISO8601_SEP_SIZE])
{
    uint8_t* cur_pos = formatted_date;
    int year = 0;

    if (timestamp == NULL || formatted_date == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    year = timestamp->tm_year + 1900;

    if (year < 0 || year > 9999)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(year, 4, cur_pos);

    *(cur_pos++) = '-';

    if (timestamp->tm_mon < 0 || timestamp->tm_mon > 11)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_mon + 1, 2, cur_pos);

    *(cur_pos++) = '-';

    if (timestamp->tm_mday < 1 || timestamp->tm_mday > 31)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_mday, 2, cur_pos);

    *(cur_pos++) = 'T';

    if (timestamp->tm_hour < 0 || timestamp->tm_hour > 23)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_hour, 2, cur_pos);

    *(cur_pos++) = ':';

    if (timestamp->tm_min < 0 || timestamp->tm_min > 59)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_min, 2, cur_pos);

    *(cur_pos++) = ':';

    if (timestamp->tm_sec < 0 || timestamp->tm_sec > 59)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_sec, 2, cur_pos);

    *(cur_pos++) = 'Z';

    return ATCACERT_E_SUCCESS;
}

int atcacert_date_dec_iso8601_sep(const uint8_t      formatted_date[DATEFMT_ISO8601_SEP_SIZE],
                                  atcacert_tm_utc_t* timestamp)
{
    const uint8_t* cur_pos = formatted_date;
    const uint8_t* new_pos = NULL;

    if (formatted_date == NULL || timestamp == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    memset(timestamp, 0, sizeof(*timestamp));

    new_pos = str_to_int(cur_pos, 4, &timestamp->tm_year);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;
    timestamp->tm_year -= 1900;

    if (*(cur_pos++) != '-')
    {
        return ATCACERT_E_DECODING_ERROR;  // Unexpected separator

    }
    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_mon);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;
    timestamp->tm_mon -= 1;

    if (*(cur_pos++) != '-')
    {
        return ATCACERT_E_DECODING_ERROR;  // Unexpected separator

    }
    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_mday);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    if (*(cur_pos++) != 'T')
    {
        return ATCACERT_E_DECODING_ERROR;  // Unexpected separator

    }
    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_hour);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    if (*(cur_pos++) != ':')
    {
        return ATCACERT_E_DECODING_ERROR;  // Unexpected separator

    }
    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_min);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    if (*(cur_pos++) != ':')
    {
        return ATCACERT_E_DECODING_ERROR;  // Unexpected separator

    }
    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_sec);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    if (*(cur_pos++) != 'Z')
    {
        return ATCACERT_E_DECODING_ERROR;  // Unexpected UTC marker

    }
    return ATCACERT_E_SUCCESS;
}

int atcacert_date_enc_rfc5280_utc(const atcacert_tm_utc_t* timestamp,
                                  uint8_t                  formatted_date[DATEFMT_RFC5280_UTC_SIZE])
{
    uint8_t* cur_pos = formatted_date;
    int year = 0;

    if (timestamp == NULL || formatted_date == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    year = timestamp->tm_year + 1900;

    if (year >= 1950 && year <= 1999)
    {
        year = year - 1900;
    }
    else if (year >= 2000 && year <= 2049)
    {
        year = year - 2000;
    }
    else
    {
        return ATCACERT_E_INVALID_DATE;  // Year out of range for RFC2459 UTC format
    }
    cur_pos = uint_to_str(year, 2, cur_pos);

    if (timestamp->tm_mon < 0 || timestamp->tm_mon > 11)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_mon + 1, 2, cur_pos);

    if (timestamp->tm_mday < 1 || timestamp->tm_mday > 31)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_mday, 2, cur_pos);

    if (timestamp->tm_hour < 0 || timestamp->tm_hour > 23)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_hour, 2, cur_pos);

    if (timestamp->tm_min < 0 || timestamp->tm_min > 59)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_min, 2, cur_pos);

    if (timestamp->tm_sec < 0 || timestamp->tm_sec > 59)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_sec, 2, cur_pos);

    *(cur_pos++) = 'Z';

    return ATCACERT_E_SUCCESS;
}

int atcacert_date_dec_rfc5280_utc(const uint8_t      formatted_date[DATEFMT_RFC5280_UTC_SIZE],
                                  atcacert_tm_utc_t* timestamp)
{
    const uint8_t* cur_pos = formatted_date;
    const uint8_t* new_pos = NULL;

    if (formatted_date == NULL || timestamp == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    memset(timestamp, 0, sizeof(*timestamp));

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_year);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;
    if (timestamp->tm_year < 50)
    {
        timestamp->tm_year += 2000;
    }
    else
    {
        timestamp->tm_year += 1900;
    }
    timestamp->tm_year -= 1900;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_mon);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;
    timestamp->tm_mon -= 1;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_mday);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_hour);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_min);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_sec);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    if (*(cur_pos++) != 'Z')
    {
        return ATCACERT_E_DECODING_ERROR;  // Unexpected UTC marker

    }
    return ATCACERT_E_SUCCESS;
}

int atcacert_date_enc_rfc5280_gen(const atcacert_tm_utc_t* timestamp,
                                  uint8_t                  formatted_date[DATEFMT_RFC5280_GEN_SIZE])
{
    uint8_t* cur_pos = formatted_date;
    int year = 0;

    if (timestamp == NULL || formatted_date == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    year = timestamp->tm_year + 1900;

    if (year < 0 || year > 9999)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(year, 4, cur_pos);

    if (timestamp->tm_mon < 0 || timestamp->tm_mon > 11)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_mon + 1, 2, cur_pos);

    if (timestamp->tm_mday < 1 || timestamp->tm_mday > 31)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_mday, 2, cur_pos);

    if (timestamp->tm_hour < 0 || timestamp->tm_hour > 23)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_hour, 2, cur_pos);

    if (timestamp->tm_min < 0 || timestamp->tm_min > 59)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_min, 2, cur_pos);

    if (timestamp->tm_sec < 0 || timestamp->tm_sec > 59)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    cur_pos = uint_to_str(timestamp->tm_sec, 2, cur_pos);

    *(cur_pos++) = 'Z';

    return ATCACERT_E_SUCCESS;
}

int atcacert_date_dec_rfc5280_gen(const uint8_t      formatted_date[DATEFMT_RFC5280_GEN_SIZE],
                                  atcacert_tm_utc_t* timestamp)
{
    const uint8_t* cur_pos = formatted_date;
    const uint8_t* new_pos = NULL;

    if (formatted_date == NULL || timestamp == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    memset(timestamp, 0, sizeof(*timestamp));

    new_pos = str_to_int(cur_pos, 4, &timestamp->tm_year);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;
    timestamp->tm_year -= 1900;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_mon);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;
    timestamp->tm_mon -= 1;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_mday);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_hour);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_min);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    new_pos = str_to_int(cur_pos, 2, &timestamp->tm_sec);
    if (new_pos == cur_pos)
    {
        return ATCACERT_E_DECODING_ERROR;  // There was a problem converting the string to a number
    }
    cur_pos = new_pos;

    if (*(cur_pos++) != 'Z')
    {
        return ATCACERT_E_DECODING_ERROR;  // Unexpected UTC marker

    }
    return ATCACERT_E_SUCCESS;
}

static int is_leap_year(int year)
{
    return (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0));
}

static uint32_t get_year_secs(int year)
{
    if (is_leap_year(year))
    {
        return (31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31) * 86400;
    }
    else
    {
        return (31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31) * 86400;
    }
}

static uint32_t get_month_secs(int year, int mon)
{
    static const uint32_t month_secs[] = { 2678400, 2419200, 2678400, 2592000, 2678400, 2592000, 2678400, 2678400, 2592000, 2678400, 2592000, 2678400 };

    if (mon == 1 && is_leap_year(year))
    {
        return 2505600;
    }
    else if (mon < 12)
    {
        return month_secs[mon];
    }
    else
    {
        return 0;
    }
}

static atcacert_tm_utc_t *atcacert_gmtime32(const uint32_t *posix_time, atcacert_tm_utc_t *result)
{
    uint32_t secs_remaining = *posix_time;
    uint32_t secs = 0;

    result->tm_year = 1970;
    result->tm_mon = 0;
    result->tm_mday = 1;
    result->tm_hour = 0;
    result->tm_min = 0;
    result->tm_sec = 0;

    secs = get_year_secs(result->tm_year);
    while (secs_remaining >= secs)
    {
        result->tm_year++;
        secs_remaining -= secs;
        secs = get_year_secs(result->tm_year);
    }

    secs = get_month_secs(result->tm_year, result->tm_mon);
    while (secs_remaining >= secs)
    {
        result->tm_mon++;
        secs_remaining -= secs;
        secs = get_month_secs(result->tm_year, result->tm_mon);
    }

    result->tm_year -= 1900;

    result->tm_mday += secs_remaining / 86400;
    secs_remaining %= 86400;

    result->tm_hour += secs_remaining / 3600;
    secs_remaining %= 3600;

    result->tm_min += secs_remaining / 60;
    secs_remaining %= 60;

    result->tm_sec += secs_remaining;

    return result;
}

static uint32_t atcacert_mkgmtime32(const atcacert_tm_utc_t *timeptr)
{
    uint32_t posix_time = 0;
    int cur_value = 0;
    int year = timeptr->tm_year + 1900;

    cur_value = year - 1;
    while (cur_value >= 1970)
    {
        posix_time += get_year_secs(cur_value--);
    }

    cur_value = timeptr->tm_mon - 1;
    while (cur_value >= 0)
    {
        posix_time += get_month_secs(year, cur_value--);
    }

    posix_time += (uint32_t)(timeptr->tm_mday - 1) * 86400;
    posix_time += (uint32_t)timeptr->tm_hour * 3600;
    posix_time += (uint32_t)timeptr->tm_min * 60;
    posix_time += (uint32_t)timeptr->tm_sec;

    return posix_time;
}

static int atcacert_date_enc_posix_uint32(const atcacert_tm_utc_t* timestamp, uint32_t* posix_uint32)
{
    //atcacert_tm_utc_t timestamp_nc;
    //time_t posix_time = 0;
    int year = 0;

    if (timestamp == NULL || posix_uint32 == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    year = timestamp->tm_year + 1900;

    if (year > 2106 || year < 1970)
    {
        return ATCACERT_E_INVALID_DATE;  //Timestamp out of range for POSIX time.
    }
    if (timestamp->tm_mon < 0 || timestamp->tm_mon > 11)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    if (timestamp->tm_mday < 1 || timestamp->tm_mday > 31)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    if (timestamp->tm_hour < 0 || timestamp->tm_hour > 23)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    if (timestamp->tm_min < 0 || timestamp->tm_min > 59)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    if (timestamp->tm_sec < 0 || timestamp->tm_sec > 59)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    // Check for date past max date for POSIX time
    if (year == 2106)
    {
        if (timestamp->tm_mon > 1)
        {
            return ATCACERT_E_INVALID_DATE;
        }
        if (timestamp->tm_mon == 1)
        {
            if (timestamp->tm_mday > 7)
            {
                return ATCACERT_E_INVALID_DATE;
            }
            if (timestamp->tm_mday == 7)
            {
                if (timestamp->tm_hour > 6)
                {
                    return ATCACERT_E_INVALID_DATE;
                }
                if (timestamp->tm_hour == 6)
                {
                    if (timestamp->tm_min > 28)
                    {
                        return ATCACERT_E_INVALID_DATE;
                    }
                    if (timestamp->tm_min == 28)
                    {
                        if (timestamp->tm_sec > 14)
                        {
                            return ATCACERT_E_INVALID_DATE;
                        }
                    }
                }
            }
        }
    }

//#ifdef WIN32
//	timestamp_nc = *timestamp;
//	posix_time = _mkgmtime(&timestamp_nc);
//	if (posix_time == -1)
//		return ATCACERT_E_INVALID_DATE;
//#elif defined _BSD_SOURCE || defined _SVID_SOURCE
//	timestamp_nc = *timestamp;
//	posix_time = timegm(&timestamp_nc);
//	if (posix_time == -1)
//		return ATCACERT_E_INVALID_DATE;
//#else
//	// In order for this to work, we need to make sure mktime is using GMT. Since this can
//	// vary from system to system, I'm putting this "assert" in every time since it may not
//	// be caught early on.
//	memset(&timestamp_nc, 0, sizeof(timestamp_nc));
//	timestamp_nc.tm_year = 2013 - 1900;
//	timestamp_nc.tm_mon = 11 - 1;
//	timestamp_nc.tm_mday = 10;
//	timestamp_nc.tm_hour = 9;
//	timestamp_nc.tm_min = 8;
//	timestamp_nc.tm_sec = 7;
//	posix_time = mktime(&timestamp_nc);
//	if (posix_time != 1384074487)
//		return ATCACERT_E_UNIMPLEMENTED; // mktime isn't using GMT as the timezone, this needs to be fixed
//
//	timestamp_nc = *timestamp;
//	posix_time = mktime(&timestamp_nc);
//	if (posix_time == -1)
//		return ATCACERT_E_INVALID_DATE;
//#endif
//
//	*posix_uint32 = (uint32_t)posix_time;

    *posix_uint32 = atcacert_mkgmtime32(timestamp);

    return ATCACERT_E_SUCCESS;
}

int atcacert_date_enc_posix_uint32_be(const atcacert_tm_utc_t* timestamp,
                                      uint8_t                  formatted_date[DATEFMT_POSIX_UINT32_BE_SIZE])
{
    uint32_t posix_uint32 = 0;
    int ret = 0;

    if (timestamp == NULL || formatted_date == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    ret = atcacert_date_enc_posix_uint32(timestamp, &posix_uint32);
    if (ret != ATCACERT_E_SUCCESS)
    {
        return ret;
    }

    formatted_date[0] = (uint8_t)((posix_uint32 >> 24) & 0xFF);
    formatted_date[1] = (uint8_t)((posix_uint32 >> 16) & 0xFF);
    formatted_date[2] = (uint8_t)((posix_uint32 >> 8) & 0xFF);
    formatted_date[3] = (uint8_t)((posix_uint32 >> 0) & 0xFF);

    return ATCACERT_E_SUCCESS;
}

static int atcacert_date_dec_posix_uint32(uint32_t           posix_uint32,
                                          atcacert_tm_utc_t* timestamp)
{
//#ifdef WIN32
//	time_t posix_time = (time_t)posix_uint32;
//	errno_t ret = 0;
//
//	if (timestamp == NULL)
//		return ATCACERT_E_BAD_PARAMS;
//
//	memset(timestamp, 0, sizeof(*timestamp));
//	ret = gmtime_s(timestamp, &posix_time);
//	if (ret != 0)
//		return ATCACERT_E_DECODING_ERROR; // Failed to convert to timestamp structure
//#else
//	time_t posix_time = (time_t)posix_uint32;
//	atcacert_tm_utc_t* ret = NULL;
//	if (timestamp == NULL)
//		return ATCACERT_E_BAD_PARAMS;
//
//	memset(timestamp, 0, sizeof(*timestamp));
//	ret = gmtime_r(&posix_time, timestamp);
//	if (ret == NULL)
//		return ATCACERT_E_DECODING_ERROR; // Failed to convert to timestamp structure
//#endif
    atcacert_gmtime32(&posix_uint32, timestamp);

    return ATCACERT_E_SUCCESS;
}

int atcacert_date_dec_posix_uint32_be(const uint8_t      formatted_date[DATEFMT_POSIX_UINT32_BE_SIZE],
                                      atcacert_tm_utc_t* timestamp)
{
    uint32_t posix_uint32 = 0;

    if (formatted_date == NULL || timestamp == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    posix_uint32 =
        ((uint32_t)formatted_date[0] << 24) |
        ((uint32_t)formatted_date[1] << 16) |
        ((uint32_t)formatted_date[2] << 8) |
        ((uint32_t)formatted_date[3]);

    return atcacert_date_dec_posix_uint32(posix_uint32, timestamp);
}

int atcacert_date_enc_posix_uint32_le(const atcacert_tm_utc_t* timestamp,
                                      uint8_t                  formatted_date[DATEFMT_POSIX_UINT32_LE_SIZE])
{
    uint32_t posix_uint32 = 0;
    int ret = 0;

    if (timestamp == NULL || formatted_date == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    ret = atcacert_date_enc_posix_uint32(timestamp, &posix_uint32);
    if (ret != ATCACERT_E_SUCCESS)
    {
        return ret;
    }

    formatted_date[0] = (uint8_t)((posix_uint32 >> 0) & 0xFF);
    formatted_date[1] = (uint8_t)((posix_uint32 >> 8) & 0xFF);
    formatted_date[2] = (uint8_t)((posix_uint32 >> 16) & 0xFF);
    formatted_date[3] = (uint8_t)((posix_uint32 >> 24) & 0xFF);

    return ATCACERT_E_SUCCESS;
}

int atcacert_date_dec_posix_uint32_le(const uint8_t      formatted_date[DATEFMT_POSIX_UINT32_LE_SIZE],
                                      atcacert_tm_utc_t* timestamp)
{
    uint32_t posix_uint32 = 0;

    if (formatted_date == NULL || timestamp == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    posix_uint32 =
        ((uint32_t)formatted_date[3] << 24) |
        ((uint32_t)formatted_date[2] << 16) |
        ((uint32_t)formatted_date[1] << 8) |
        ((uint32_t)formatted_date[0]);

    return atcacert_date_dec_posix_uint32(posix_uint32, timestamp);
}

int atcacert_date_enc_compcert(const atcacert_tm_utc_t* issue_date,
                               uint8_t                  expire_years,
                               uint8_t                  enc_dates[3])
{
    /*
     * Issue and expire dates are compressed/encoded as below
     * +---------------+---------------+---------------+
     * | Byte 1        | Byte 2        | Byte 3        |
     * +---------------+---------------+---------------+
     * | | | | | | | | | | | | | | | | | | | | | | | | |
     * | 5 bits  | 4 bits| 5 bits  | 5 bits  | 5 bits  |
     * | Year    | Month | Day     | Hour    | Expire  |
     * |         |       |         |         | Years   |
     * +---------+-------+---------+---------+---------+
     *
     * Minutes and seconds are always zero.
     */
    if (issue_date == NULL || enc_dates == NULL)
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    if ((issue_date->tm_year + 1900) < 2000 || (issue_date->tm_year + 1900) > 2031)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    if (issue_date->tm_mon < 0 || issue_date->tm_mon > 11)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    if (issue_date->tm_mday < 1 || issue_date->tm_mday > 31)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    if (issue_date->tm_hour < 0 || issue_date->tm_hour > 23)
    {
        return ATCACERT_E_INVALID_DATE;
    }
    if (expire_years > 31)
    {
        return ATCACERT_E_INVALID_DATE;
    }

    memset(enc_dates, 0, 3);

    enc_dates[0] = (enc_dates[0] & 0x07) | (uint8_t)(((issue_date->tm_year + 1900 - 2000) & 0x1F) << 3);
    enc_dates[0] = (uint8_t)((enc_dates[0] & 0xF8) | (((issue_date->tm_mon + 1) & 0x0F) >> 1));
    enc_dates[1] = (uint8_t)((enc_dates[1] & 0x7F) | (((issue_date->tm_mon + 1) & 0x0F) << 7));
    enc_dates[1] = (uint8_t)((enc_dates[1] & 0x83) | ((issue_date->tm_mday & 0x1F) << 2));
    enc_dates[1] = (uint8_t)((enc_dates[1] & 0xFC) | ((issue_date->tm_hour & 0x1F) >> 3));
    enc_dates[2] = (uint8_t)((enc_dates[2] & 0x1F) | ((issue_date->tm_hour & 0x1F) << 5));
    enc_dates[2] = (uint8_t)((enc_dates[2] & 0xE0) | (expire_years & 0x1F));

    return ATCACERT_E_SUCCESS;
}

int atcacert_date_dec_compcert(const uint8_t          enc_dates[3],
                               atcacert_date_format_t expire_date_format,
                               atcacert_tm_utc_t*     issue_date,
                               atcacert_tm_utc_t*     expire_date)
{
    int ret = ATCACERT_E_SUCCESS;
    uint8_t expire_years = 0;

    /*
     * Issue and expire dates are compressed/encoded as below
     * +---------------+---------------+---------------+
     * | Byte 1        | Byte 2        | Byte 3        |
     * +---------------+---------------+---------------+
     * | | | | | | | | | | | | | | | | | | | | | | | | |
     * | 5 bits  | 4 bits| 5 bits  | 5 bits  | 5 bits  |
     * | Year    | Month | Day     | Hour    | Expire  |
     * |         |       |         |         | Years   |
     * +---------+-------+---------+---------+---------+
     *
     * Minutes and seconds are always zero.
     */

    if (enc_dates == NULL || issue_date == NULL || expire_date == NULL || expire_date_format >= sizeof(ATCACERT_DATE_FORMAT_SIZES) / sizeof(ATCACERT_DATE_FORMAT_SIZES[0]))
    {
        return ATCACERT_E_BAD_PARAMS;
    }

    memset(issue_date, 0, sizeof(*issue_date));
    memset(expire_date, 0, sizeof(*expire_date));

    issue_date->tm_year = (enc_dates[0] >> 3) + 2000 - 1900;
    issue_date->tm_mon  = (((enc_dates[0] & 0x07) << 1) | ((enc_dates[1] & 0x80) >> 7)) - 1;
    issue_date->tm_mday = ((enc_dates[1] & 0x7C) >> 2);
    issue_date->tm_hour = ((enc_dates[1] & 0x03) << 3) | ((enc_dates[2] & 0xE0) >> 5);

    expire_years = (enc_dates[2] & 0x1F);

    if (expire_years != 0)
    {
        expire_date->tm_year = issue_date->tm_year + expire_years;
        expire_date->tm_mon  = issue_date->tm_mon;
        expire_date->tm_mday = issue_date->tm_mday;
        expire_date->tm_hour = issue_date->tm_hour;
    }
    else
    {
        // Expire years of 0, means no expiration. Set to max date for the given expiration date format.
        ret = atcacert_date_get_max_date(expire_date_format, expire_date);
        if (ret != ATCACERT_E_SUCCESS)
        {
            return ret;
        }
    }

    return ATCACERT_E_SUCCESS;
}
