/*
 *  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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif

#include "syshead.h"

#include "otime.h"

#include "memdbg.h"

time_t now = 0;            /* GLOBAL */

#if TIME_BACKTRACK_PROTECTION

static time_t now_adj = 0; /* GLOBAL */
time_t now_usec = 0;       /* GLOBAL */

/*
 * Try to filter out time instability caused by the system
 * clock backtracking or jumping forward.
 */

void
update_now(const time_t system_time)
{
    const int forward_threshold = 86400; /* threshold at which to dampen forward jumps */
    const int backward_trigger  = 10;  /* backward jump must be >= this many seconds before we adjust */
    time_t real_time = system_time + now_adj;

    if (real_time > now)
    {
        const time_t overshoot = real_time - now - 1;
        if (overshoot > forward_threshold && now_adj >= overshoot)
        {
            now_adj -= overshoot;
            real_time -= overshoot;
        }
        now = real_time;
    }
    else if (real_time < now - backward_trigger)
    {
        now_adj += (now - real_time);
    }
}

void
update_now_usec(struct timeval *tv)
{
    const time_t last = now;
    update_now(tv->tv_sec);
    if (now > last || (now == last && tv->tv_usec > now_usec))
    {
        now_usec = tv->tv_usec;
    }
}

#endif /* TIME_BACKTRACK_PROTECTION */

/*
 * Return a numerical string describing a struct timeval.
 */
const char *
tv_string(const struct timeval *tv, struct gc_arena *gc)
{
    struct buffer out = alloc_buf_gc(64, gc);
    buf_printf(&out, "[%lld/%ld]",
               (long long)tv->tv_sec,
               (long)tv->tv_usec);
    return BSTR(&out);
}

/*
 * Return an ascii string describing an absolute
 * date/time in a struct timeval.
 *
 */
const char *
tv_string_abs(const struct timeval *tv, struct gc_arena *gc)
{
    return time_string((time_t) tv->tv_sec,
                       (long) tv->tv_usec,
                       true,
                       gc);
}

/* 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 buffer out = alloc_buf_gc(64, gc);
    struct timeval tv;

    if (t)
    {
        tv.tv_sec = t;
        tv.tv_usec = usec;
    }
    else
    {
        gettimeofday(&tv, NULL);
    }

    t = tv.tv_sec;
    buf_printf(&out, "%s", ctime(&t));
    buf_rmtail(&out, '\n');

    if (show_usec && tv.tv_usec)
    {
        buf_printf(&out, " us=%ld", (long)tv.tv_usec);
    }

    return BSTR(&out);
}

/*
 * Limit the frequency of an event stream.
 *
 * Used to control maximum rate of new
 * incoming connections.
 */

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

    ASSERT(max >= 0 && per >= 0);

    ALLOC_OBJ(f, struct frequency_limit);
    f->max = max;
    f->per = per;
    f->n = 0;
    f->reset = 0;
    return f;
}

void
frequency_limit_free(struct frequency_limit *f)
{
    free(f);
}

bool
frequency_limit_event_allowed(struct frequency_limit *f)
{
    if (f->per)
    {
        bool ret;
        if (now >= f->reset + f->per)
        {
            f->reset = now;
            f->n = 0;
        }
        ret = (++f->n <= f->max);
        return ret;
    }
    else
    {
        return true;
    }
}

#ifdef TIME_TEST
void
time_test(void)
{
    struct timeval tv;
    time_t t;
    int i;
    for (i = 0; i < 10000; ++i)
    {
        t = time(NULL);
        gettimeofday(&tv, NULL);
#if 1
        msg(M_INFO, "t=%lld s=%lld us=%ld",
            (long long)t,
            (long long)tv.tv_sec,
            (long)tv.tv_usec);
#endif
    }
}
#endif