/*
 *  Host Resources MIB - utility functions - hr_utils.c
 *
 */


#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>
#include <sys/types.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <ctype.h>
#if HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <net-snmp/types.h>
#include <net-snmp/library/snmp-tc.h>   /* for "internal" definitions */
#include <net-snmp/library/snmp_api.h>

netsnmp_feature_child_of(snmp_tc_all, libnetsnmp);

netsnmp_feature_child_of(netsnmp_dateandtime_set_buf_from_vars, netsnmp_unused);
netsnmp_feature_child_of(date_n_time, snmp_tc_all);
netsnmp_feature_child_of(ctime_to_timet, snmp_tc_all);
netsnmp_feature_child_of(check_rowstatus_with_storagetype_transition, snmp_tc_all);

#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_DATEANDTIME_SET_BUF_FROM_VARS
/*
  DateAndTime ::= TEXTUAL-CONVENTION
    DISPLAY-HINT "2d-1d-1d,1d:1d:1d.1d,1a1d:1d"
    STATUS       current
    DESCRIPTION
            "A date-time specification.

            field  octets  contents                  range
            -----  ------  --------                  -----
              1      1-2   year*                     0..65536
              2       3    month                     1..12
              3       4    day                       1..31
              4       5    hour                      0..23
              5       6    minutes                   0..59
              6       7    seconds                   0..60
                           (use 60 for leap-second)
              7       8    deci-seconds              0..9
              8       9    direction from UTC        '+' / '-'
              9      10    hours from UTC*           0..13
             10      11    minutes from UTC          0..59

            * Notes:
            - the value of year is in network-byte order
            - daylight saving time in New Zealand is +13

            For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be
            displayed as:

                             1992-5-26,13:30:15.0,-4:0

            Note that if only local time is known, then timezone
            information (fields 8-10) is not present."
    SYNTAX       OCTET STRING (SIZE (8 | 11))
*/

int
netsnmp_dateandtime_set_buf_from_vars(u_char *buf, size_t *bufsize,
                                      u_short year, u_char month, u_char day,
                                      u_char hour, u_char minutes,
                                      u_char seconds, u_char deci_seconds,
                                      int utc_offset_direction,
                                      u_char utc_offset_hours,
                                      u_char utc_offset_minutes)
{
    u_short tmp_year = htons(year);

    /*
     * if we have a utc offset, need 11 bytes. Otherwise we
     * just need 8 bytes.
     */
    if(utc_offset_direction) {
        if(*bufsize < 11)
            return SNMPERR_RANGE;

        /*
         * set utc offset data
         */
        buf[8] = (utc_offset_direction < 0) ? '-' : '+';
        buf[9] = utc_offset_hours;
        buf[10] = utc_offset_minutes;
        *bufsize = 11;
    }
    else if(*bufsize < 8)
        return SNMPERR_RANGE;
    else
        *bufsize = 8;

    /*
     * set basic date/time data
     */
    memcpy(buf, &tmp_year, sizeof(tmp_year));
    buf[2] = month;
    buf[3] = day;
    buf[4] = hour;
    buf[5] = minutes;
    buf[6] = seconds;
    buf[7] = deci_seconds;

    return SNMPERR_SUCCESS;
}
#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_DATEANDTIME_SET_BUF_FROM_VARS */

#ifndef NETSNMP_FEATURE_REMOVE_DATE_N_TIME
u_char         *
date_n_time(const time_t * when, size_t * length)
{
    struct tm      *tm_p;
    static u_char   string[11];
    unsigned short yauron;

    /*
     * Null time
     */
    if (when == NULL || *when == 0 || *when == (time_t) - 1) {
        string[0] = 0;
        string[1] = 0;
        string[2] = 1;
        string[3] = 1;
        string[4] = 0;
        string[5] = 0;
        string[6] = 0;
        string[7] = 0;
        *length = 8;
        return string;
    }


    /*
     * Basic 'local' time handling
     */
    tm_p = localtime(when);
    yauron = tm_p->tm_year + 1900;
    string[0] = (u_char)(yauron >> 8);
    string[1] = (u_char)yauron;
    string[2] = tm_p->tm_mon + 1;
    string[3] = tm_p->tm_mday;
    string[4] = tm_p->tm_hour;
    string[5] = tm_p->tm_min;
    string[6] = tm_p->tm_sec;
    string[7] = 0;
    *length = 8;

#if defined(HAVE_STRUCT_TM_TM_GMTOFF) || HAVE_DECL_TIMEZONE
    /*
     * Timezone offset
     */
    {
#ifdef HAVE_STRUCT_TM_TM_GMTOFF
    const int tzoffset = -tm_p->tm_gmtoff;   /* Seconds east of UTC */
#else
    const int tzoffset = timezone;           /* Seconds west of UTC */
#endif
    if (tzoffset > 0)
        string[8] = '-';
    else
        string[8] = '+';
    string[9] = abs(tzoffset) / 3600;
    string[10] = (abs(tzoffset) - string[9] * 3600) / 60;
    *length = 11;
    }
#endif

#if defined(SYSV) && !HAVE_STRUCT_TM_TM_GMTOFF
    /*
     * Daylight saving time
     */
    if (tm_p->tm_isdst > 0) {
        /*
         * Assume add one hour 
         */
        if (string[8] == '-')
            --string[9];
        else
            ++string[9];

        if (string[9] == 0)
            string[8] = '+';
    }
#endif

    return string;
}
#endif /* NETSNMP_FEATURE_REMOVE_DATE_N_TIME */

#ifndef NETSNMP_FEATURE_REMOVE_CTIME_TO_TIMET
time_t
ctime_to_timet(const char *str)
{
    struct tm       tm;

    if (strlen(str) < 24)
        return 0;

    /*
     * Month 
     */
    if (!strncmp(str + 4, "Jan", 3))
        tm.tm_mon = 0;
    else if (!strncmp(str + 4, "Feb", 3))
        tm.tm_mon = 1;
    else if (!strncmp(str + 4, "Mar", 3))
        tm.tm_mon = 2;
    else if (!strncmp(str + 4, "Apr", 3))
        tm.tm_mon = 3;
    else if (!strncmp(str + 4, "May", 3))
        tm.tm_mon = 4;
    else if (!strncmp(str + 4, "Jun", 3))
        tm.tm_mon = 5;
    else if (!strncmp(str + 4, "Jul", 3))
        tm.tm_mon = 6;
    else if (!strncmp(str + 4, "Aug", 3))
        tm.tm_mon = 7;
    else if (!strncmp(str + 4, "Sep", 3))
        tm.tm_mon = 8;
    else if (!strncmp(str + 4, "Oct", 3))
        tm.tm_mon = 9;
    else if (!strncmp(str + 4, "Nov", 3))
        tm.tm_mon = 10;
    else if (!strncmp(str + 4, "Dec", 3))
        tm.tm_mon = 11;
    else
        return 0;

    tm.tm_mday = atoi(str + 8);
    tm.tm_hour = atoi(str + 11);
    tm.tm_min = atoi(str + 14);
    tm.tm_sec = atoi(str + 17);
    tm.tm_year = atoi(str + 20) - 1900;

    /*
     *  Cope with timezone and DST
     */

#ifdef HAVE_STRUCT_TM_TM_ISDST
#if HAVE_DECL_DAYLIGHT
    tm.tm_isdst = !!daylight;
#else
    tm.tm_isdst = 0;
#endif
#if HAVE_DECL_TIMEZONE && defined(HAVE_SCALAR_TIMEZONE)
    tm.tm_sec -= timezone;
#endif
#endif

    return (mktime(&tm));
}
#endif /* NETSNMP_FEATURE_REMOVE_CTIME_TO_TIMET */

/*
 * blatantly lifted from opensnmp 
 */
char
check_rowstatus_transition(int oldValue, int newValue)
{
    /*
     * From the SNMPv2-TC MIB:
     *                                          STATE
     *               +--------------+-----------+-------------+-------------
     *               |      A       |     B     |      C      |      D
     *               |              |status col.|status column|
     *               |status column |    is     |      is     |status column
     *     ACTION    |does not exist|  notReady | notInService|  is active
     * --------------+--------------+-----------+-------------+-------------
     * set status    |noError    ->D|inconsist- |inconsistent-|inconsistent-
     * column to     |       or     |   entValue|        Value|        Value
     * createAndGo   |inconsistent- |           |             |
     *               |         Value|           |             |
     * --------------+--------------+-----------+-------------+-------------
     * set status    |noError  see 1|inconsist- |inconsistent-|inconsistent-
     * column to     |       or     |   entValue|        Value|        Value
     * createAndWait |wrongValue    |           |             |
     * --------------+--------------+-----------+-------------+-------------
     * set status    |inconsistent- |inconsist- |noError      |noError
     * column to     |         Value|   entValue|             |
     * active        |              |           |             |
     *               |              |     or    |             |
     *               |              |           |             |
     *               |              |see 2   ->D|see 8     ->D|          ->D
     * --------------+--------------+-----------+-------------+-------------
     * set status    |inconsistent- |inconsist- |noError      |noError   ->C
     * column to     |         Value|   entValue|             |
     * notInService  |              |           |             |
     *               |              |     or    |             |      or
     *               |              |           |             |
     *               |              |see 3   ->C|          ->C|see 6
     * --------------+--------------+-----------+-------------+-------------
     * set status    |noError       |noError    |noError      |noError   ->A
     * column to     |              |           |             |      or
     * destroy       |           ->A|        ->A|          ->A|see 7
     * --------------+--------------+-----------+-------------+-------------
     * set any other |see 4         |noError    |noError      |see 5
     * column to some|              |           |             |
     * value         |              |      see 1|          ->C|          ->D
     * --------------+--------------+-----------+-------------+-------------
     
     *             (1) goto B or C, depending on information available to the
     *             agent.
     
     *             (2) if other variable bindings included in the same PDU,
     *             provide values for all columns which are missing but
     *             required, and all columns have acceptable values, then
     *             return noError and goto D.
     
     *             (3) if other variable bindings included in the same PDU,
     *             provide legal values for all columns which are missing but
     *             required, then return noError and goto C.
     
     *             (4) at the discretion of the agent, the return value may be
     *             either:
     
     *                  inconsistentName:  because the agent does not choose to
     *                  create such an instance when the corresponding
     *                  RowStatus instance does not exist, or
     
     *                  inconsistentValue:  if the supplied value is
     *                  inconsistent with the state of some other MIB object's
     *                  value, or
     
     *                  noError: because the agent chooses to create the
     *                  instance.
     
     *             If noError is returned, then the instance of the status
     *             column must also be created, and the new state is B or C,
     *             depending on the information available to the agent.  If
     *             inconsistentName or inconsistentValue is returned, the row
     *             remains in state A.
     
     *             (5) depending on the MIB definition for the column/table,
     *             either noError or inconsistentValue may be returned.
     
     *             (6) the return value can indicate one of the following
     *             errors:
     
     *                  wrongValue: because the agent does not support
     *                  notInService (e.g., an agent which does not support
     *                  createAndWait), or
     
     *                  inconsistentValue: because the agent is unable to take
     *                  the row out of service at this time, perhaps because it
     *                  is in use and cannot be de-activated.
     
     *             (7) the return value can indicate the following error:
     
     *                  inconsistentValue: because the agent is unable to
     *                  remove the row at this time, perhaps because it is in
     *                  use and cannot be de-activated.
     
     *             (8) the transition to D can fail, e.g., if the values of the
     *             conceptual row are inconsistent, then the error code would
     *             be inconsistentValue.
     
     *             NOTE: Other processing of (this and other varbinds of) the
     *             set request may result in a response other than noError
     *             being returned, e.g., wrongValue, noCreation, etc.
     */

    switch (newValue) {
        /*
         * these two end up being equivelent as far as checking the 
         * status goes, although the final states are based on the 
         * newValue. 
         */
    case RS_ACTIVE:
    case RS_NOTINSERVICE:
        if (oldValue == RS_NOTINSERVICE || oldValue == RS_ACTIVE)
            ;
        else
            return SNMP_ERR_INCONSISTENTVALUE;
        break;

    case RS_NOTREADY:
        /*
         * Illegal set value. 
         */
        return SNMP_ERR_WRONGVALUE;
        break;

    case RS_CREATEANDGO:
    case RS_CREATEANDWAIT:
        if (oldValue != RS_NONEXISTENT)
            /*
             * impossible, we already exist. 
             */
            return SNMP_ERR_INCONSISTENTVALUE;
        break;

    case RS_DESTROY:
        break;

    default:
        return SNMP_ERR_WRONGVALUE;
        break;
    }

    return SNMP_ERR_NOERROR;
}

#ifndef NETSNMP_FEATURE_REMOVE_CHECK_ROWSTATUS_WITH_STORAGETYPE_TRANSITION
char
check_rowstatus_with_storagetype_transition(int oldValue, int newValue,
                                            int oldStorage)
{
    /*
     * can not destroy permanent or readonly rows
     */
    if ((RS_DESTROY == newValue) &&
        ((SNMP_STORAGE_PERMANENT == oldStorage) ||
         (SNMP_STORAGE_READONLY == oldStorage)))
        return SNMP_ERR_WRONGVALUE;

    return check_rowstatus_transition(oldValue, newValue);
}
#endif /* NETSNMP_FEATURE_REMOVE_CHECK_ROWSTATUS_WITH_STORAGETYPE_TRANSITION */

netsnmp_feature_child_of(check_storage_transition, snmp_tc_all);
#ifndef NETSNMP_FEATURE_REMOVE_CHECK_STORAGE_TRANSITION
char
check_storage_transition(int oldValue, int newValue)
{
    /*
     * From the SNMPv2-TC MIB:
     
     *             "Describes the memory realization of a conceptual row.  A
     *             row which is volatile(2) is lost upon reboot.  A row which
     *             is either nonVolatile(3), permanent(4) or readOnly(5), is
     *             backed up by stable storage.  A row which is permanent(4)
     *             can be changed but not deleted.  A row which is readOnly(5)
     *             cannot be changed nor deleted.
     
     *             If the value of an object with this syntax is either
     *             permanent(4) or readOnly(5), it cannot be written.
     *             Conversely, if the value is either other(1), volatile(2) or
     *             nonVolatile(3), it cannot be modified to be permanent(4) or
     *             readOnly(5).  (All illegal modifications result in a
     *             'wrongValue' error.)
     
     *             Every usage of this textual convention is required to
     *             specify the columnar objects which a permanent(4) row must
     *             at a minimum allow to be writable."
     */
    switch (oldValue) {
    case SNMP_STORAGE_PERMANENT:
    case SNMP_STORAGE_READONLY:
        return SNMP_ERR_INCONSISTENTVALUE;

    case SNMP_STORAGE_NONE:
    case SNMP_STORAGE_OTHER:
    case SNMP_STORAGE_VOLATILE:
    case SNMP_STORAGE_NONVOLATILE:
        if (newValue == SNMP_STORAGE_PERMANENT ||
            newValue == SNMP_STORAGE_READONLY)
            return SNMP_ERR_INCONSISTENTVALUE;
    }

    return SNMP_ERR_NOERROR;
}
#endif /* NETSNMP_FEATURE_REMOVE_CHECK_STORAGE_TRANSITION */
