/*
 *  OpenVPN -- An application to securely tunnel IP networks
 *             over a single TCP/UDP port, with support for SSL/TLS-based
 *             session authentication and key exchange,
 *             packet encryption, packet authentication, and
 *             packet compression.
 *
 *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifndef OTIME_H
#define OTIME_H

#include "common.h"
#include "integer.h"
#include "buffer.h"

struct frequency_limit
{
    int max;
    int per;
    int n;
    time_t reset;
};

struct frequency_limit *frequency_limit_init(int max, int per);

void frequency_limit_free(struct frequency_limit *f);

bool frequency_limit_event_allowed(struct frequency_limit *f);

/* format a time_t as ascii, or use current time if 0 */
const char *time_string(time_t t, int usec, bool show_usec, struct gc_arena *gc);

/* struct timeval functions */

const char *tv_string(const struct timeval *tv, struct gc_arena *gc);

const char *tv_string_abs(const struct timeval *tv, struct gc_arena *gc);

extern time_t now; /* updated frequently to time(NULL) */

void time_test(void);

#if TIME_BACKTRACK_PROTECTION

void update_now(const time_t system_time);

extern time_t now_usec;
void update_now_usec(struct timeval *tv);

static inline int
openvpn_gettimeofday(struct timeval *tv, void *tz)
{
    const int status = gettimeofday(tv, tz);
    if (!status)
    {
        update_now_usec(tv);
        tv->tv_sec = now;
        tv->tv_usec = now_usec;
    }
    return status;
}

static inline void
update_time(void)
{
#ifdef _WIN32
    /* on _WIN32, gettimeofday is faster than time(NULL) */
    struct timeval tv;
    openvpn_gettimeofday(&tv, NULL);
#else
    update_now(time(NULL));
#endif
}

#else /* !TIME_BACKTRACK_PROTECTION */

static inline void
update_time(void)
{
#if defined(_WIN32)
    /* on _WIN32, gettimeofday is faster than time(NULL) */
    struct timeval tv;
    if (!gettimeofday(&tv, NULL))
    {
        if (tv.tv_sec != now)
        {
            now = tv.tv_sec;
        }
    }
#else  /* if defined(_WIN32) */
    const time_t real_time = time(NULL);
    if (real_time != now)
    {
        now = real_time;
    }
#endif
}

static inline int
openvpn_gettimeofday(struct timeval *tv, void *tz)
{
    return gettimeofday(tv, tz);
}

#endif /* TIME_BACKTRACK_PROTECTION */

static inline time_t
openvpn_time(time_t *t)
{
    update_time();
    if (t)
    {
        *t = now;
    }
    return now;
}

static inline void
tv_clear(struct timeval *tv)
{
    tv->tv_sec = 0;
    tv->tv_usec = 0;
}

static inline bool
tv_defined(const struct timeval *tv)
{
    return tv->tv_sec > 0 && tv->tv_usec > 0;
}

/* return tv1 - tv2 in usec, constrained by max_seconds */
static inline int
tv_subtract(const struct timeval *tv1, const struct timeval *tv2, const unsigned int max_seconds)
{
    const int max_usec = max_seconds * 1000000;
    const int sec_diff = tv1->tv_sec - tv2->tv_sec;

    if (sec_diff > ((int)max_seconds + 10))
    {
        return max_usec;
    }
    else if (sec_diff < -((int)max_seconds + 10))
    {
        return -max_usec;
    }
    return constrain_int(sec_diff * 1000000 + (tv1->tv_usec - tv2->tv_usec), -max_usec, max_usec);
}

static inline void
tv_add(struct timeval *dest, const struct timeval *src)
{
    dest->tv_sec += src->tv_sec;
    dest->tv_usec += src->tv_usec;
    dest->tv_sec += (dest->tv_usec >> 20);
    dest->tv_usec &= 0x000FFFFF;
    if (dest->tv_usec >= 1000000)
    {
        dest->tv_usec -= 1000000;
        dest->tv_sec += 1;
    }
}

static inline bool
tv_lt(const struct timeval *t1, const struct timeval *t2)
{
    if (t1->tv_sec < t2->tv_sec)
    {
        return true;
    }
    else if (t1->tv_sec > t2->tv_sec)
    {
        return false;
    }
    else
    {
        return t1->tv_usec < t2->tv_usec;
    }
}

static inline bool
tv_le(const struct timeval *t1, const struct timeval *t2)
{
    if (t1->tv_sec < t2->tv_sec)
    {
        return true;
    }
    else if (t1->tv_sec > t2->tv_sec)
    {
        return false;
    }
    else
    {
        return t1->tv_usec <= t2->tv_usec;
    }
}

static inline bool
tv_ge(const struct timeval *t1, const struct timeval *t2)
{
    if (t1->tv_sec > t2->tv_sec)
    {
        return true;
    }
    else if (t1->tv_sec < t2->tv_sec)
    {
        return false;
    }
    else
    {
        return t1->tv_usec >= t2->tv_usec;
    }
}

static inline bool
tv_gt(const struct timeval *t1, const struct timeval *t2)
{
    if (t1->tv_sec > t2->tv_sec)
    {
        return true;
    }
    else if (t1->tv_sec < t2->tv_sec)
    {
        return false;
    }
    else
    {
        return t1->tv_usec > t2->tv_usec;
    }
}

static inline bool
tv_eq(const struct timeval *t1, const struct timeval *t2)
{
    return t1->tv_sec == t2->tv_sec && t1->tv_usec == t2->tv_usec;
}

static inline void
tv_delta(struct timeval *dest, const struct timeval *t1, const struct timeval *t2)
{
    int sec = t2->tv_sec - t1->tv_sec;
    int usec = t2->tv_usec - t1->tv_usec;

    while (usec < 0)
    {
        usec += 1000000;
        sec -= 1;
    }

    if (sec < 0)
    {
        usec = sec = 0;
    }

    dest->tv_sec = sec;
    dest->tv_usec = usec;
}

#define TV_WITHIN_SIGMA_MAX_SEC 600
#define TV_WITHIN_SIGMA_MAX_USEC (TV_WITHIN_SIGMA_MAX_SEC * 1000000)

/*
 * Is t1 and t2 within sigma microseconds of each other?
 */
static inline bool
tv_within_sigma(const struct timeval *t1, const struct timeval *t2, unsigned int sigma)
{
    const int delta = tv_subtract(t1, t2, TV_WITHIN_SIGMA_MAX_SEC); /* sigma should be less than 10 minutes */
    return -(int)sigma <= delta && delta <= (int)sigma;
}

/*
 * Used to determine in how many seconds we should be
 * called again.
 */
static inline void
interval_earliest_wakeup(interval_t *wakeup, time_t at, time_t current)
{
    if (at > current)
    {
        const interval_t delta = (interval_t) (at - current);
        if (delta < *wakeup)
        {
            *wakeup = delta;
        }
        if (*wakeup < 0)
        {
            *wakeup = 0;
        }
    }
}

#endif /* ifndef OTIME_H */