/**************************************************************
 * Copyright (C) 2001 Tali Rozin, Optical Access
 *
 *                     All Rights Reserved
 *
 * Permission to use, copy, modify and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * TALI ROZIN DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * ALEX ROZIN BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 ******************************************************************/

#include <net-snmp/net-snmp-config.h>

#ifdef HAVE_STDLIB
#include <stdlib.h>
#endif
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "statistics.h"
        /*
         * Implementation headers 
         */
#include "agutil_api.h"
#include "row_api.h"
        /*
         * File scope definitions section 
         */
        /*
         * from MIB compilation 
         */
#define MIB_DESCR	"EthStat"
#define etherStatsEntryFirstIndexBegin	11
#define IDetherStatsDroppedFrames        1
#define IDetherStatsCreateTime           2
#define IDetherStatsIndex                3
#define IDetherStatsDataSource           4
#define IDetherStatsDropEvents           5
#define IDetherStatsOctets               6
#define IDetherStatsPkts                 7
#define IDetherStatsBroadcastPkts        8
#define IDetherStatsMulticastPkts        9
#define IDetherStatsCRCAlignErrors       10
#define IDetherStatsUndersizePkts        11
#define IDetherStatsOversizePkts         12
#define IDetherStatsFragments            13
#define IDetherStatsJabbers              14
#define IDetherStatsCollisions           15
#define IDetherStatsPkts64Octets         16
#define IDetherStatsPkts65to127Octets    17
#define IDetherStatsPkts128to255Octets   18
#define IDetherStatsPkts256to511Octets   19
#define IDetherStatsPkts512to1023Octets  20
#define IDetherStatsPkts1024to1518Octets 21
#define IDetherStatsOwner                22
#define IDetherStatsStatus               23
#define Leaf_etherStatsDataSource        2
#define Leaf_etherStatsOwner             20
#define Leaf_etherStatsStatus            21
#define MIN_etherStatsIndex   1
#define MAX_etherStatsIndex   65535
     typedef struct {
         VAR_OID_T
             data_source;
         u_long
             etherStatsCreateTime;
         ETH_STATS_T
             eth;
     } CRTL_ENTRY_T;

/*
 * Main section 
 */

     static TABLE_DEFINTION_T
         StatCtrlTable;
     static TABLE_DEFINTION_T *
         table_ptr = &
         StatCtrlTable;

/*
 * Control Table RowApi Callbacks 
 */

     int
     stat_Create(RMON_ENTRY_T * eptr)
{                               /* create the body: alloc it and set defaults */
    CRTL_ENTRY_T   *body;
    static VAR_OID_T data_src_if_index_1 =
        { 11, {1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1} };

    eptr->body = AGMALLOC(sizeof(CRTL_ENTRY_T));
    if (!eptr->body)
        return -3;
    body = (CRTL_ENTRY_T *) eptr->body;

    /*
     * set defaults 
     */
    memcpy(&body->data_source, &data_src_if_index_1, sizeof(VAR_OID_T));
    body->data_source.objid[body->data_source.length - 1] =
        eptr->ctrl_index;
    eptr->owner = AGSTRDUP("Startup Mgmt");
    memset(&body->eth, 0, sizeof(ETH_STATS_T));

    return 0;
}

int
stat_Validate(RMON_ENTRY_T * eptr)
{
    /*
     * T.B.D. (system dependent) check valid interface in body->data_source;
     */

    return 0;
}

int
stat_Activate(RMON_ENTRY_T * eptr)
{
    CRTL_ENTRY_T   *body = (CRTL_ENTRY_T *) eptr->body;

    body->etherStatsCreateTime = AGUTIL_sys_up_time();

    return 0;
}

int
stat_Copy(RMON_ENTRY_T * eptr)
{
    CRTL_ENTRY_T   *body = (CRTL_ENTRY_T *) eptr->body;
    CRTL_ENTRY_T   *clone = (CRTL_ENTRY_T *) eptr->tmp;

    if (snmp_oid_compare
        (clone->data_source.objid, clone->data_source.length,
         body->data_source.objid, body->data_source.length)) {
        memcpy(&body->data_source, &clone->data_source, sizeof(VAR_OID_T));
    }

    return 0;
}

int
stat_Deactivate(RMON_ENTRY_T * eptr)
{
    CRTL_ENTRY_T   *body = (CRTL_ENTRY_T *) eptr->body;
    memset(&body->eth, 0, sizeof(ETH_STATS_T));
    return 0;
}


/***************************************************
 * Function:var_etherStats2Entry 
 * Purpose: Handles the request for etherStats2Entry variable instances
 ***************************************************/
u_char         *
var_etherStats2Entry(struct variable * vp, oid * name, size_t * length,
                     int exact, size_t * var_len,
                     WriteMethod ** write_method)
{
    static long     long_return;
    static CRTL_ENTRY_T theEntry;
    RMON_ENTRY_T   *hdr;

    *write_method = NULL;

    hdr = ROWAPI_header_ControlEntry(vp, name, length, exact, var_len,
                                     table_ptr,
                                     &theEntry, sizeof(CRTL_ENTRY_T));
    if (!hdr)
        return NULL;

    *var_len = sizeof(long);    /* default */

    switch (vp->magic) {
    case IDetherStatsDroppedFrames:
        long_return = 0;
        return (u_char *) & long_return;
    case IDetherStatsCreateTime:
        long_return = theEntry.etherStatsCreateTime;
        return (u_char *) & long_return;
    default:
        ag_trace("%s: unknown vp->magic=%d", table_ptr->name,
                 (int) vp->magic);
        ERROR_MSG("");
    };                          /* of switch by 'vp->magic'  */

    return NULL;
}


/***************************************************
 * Function:write_etherStatsEntry 
 ***************************************************/
static int
write_etherStatsEntry(int action, u_char * var_val, u_char var_val_type,
                      size_t var_val_len, u_char * statP,
                      oid * name, size_t name_len)
{
    long            long_temp;
    int             leaf_id, snmp_status;
    static int      prev_action = COMMIT;
    RMON_ENTRY_T   *hdr;
    CRTL_ENTRY_T   *cloned_body;
    CRTL_ENTRY_T   *body;

    switch (action) {
    case RESERVE1:
    case FREE:
    case UNDO:
    case ACTION:
    case COMMIT:
    default:
        snmp_status =
            ROWAPI_do_another_action(name, etherStatsEntryFirstIndexBegin,
                                     action, &prev_action, table_ptr,
                                     sizeof(CRTL_ENTRY_T));
        if (SNMP_ERR_NOERROR != snmp_status) {
            ag_trace("failed action %d with %d", action, snmp_status);
        }
        break;

    case RESERVE2:
        /*
         * get values from PDU, check them and save them in the cloned entry 
         */
        long_temp = name[etherStatsEntryFirstIndexBegin];
        leaf_id = (int) name[etherStatsEntryFirstIndexBegin - 1];
        hdr = ROWAPI_find(table_ptr, long_temp);        /* it MUST be OK */
        cloned_body = (CRTL_ENTRY_T *) hdr->tmp;
        body = (CRTL_ENTRY_T *) hdr->body;
        switch (leaf_id) {
        case Leaf_etherStatsDataSource:
            snmp_status = AGUTIL_get_oid_value(var_val, var_val_type,
                                               var_val_len,
                                               &cloned_body->data_source);
            if (SNMP_ERR_NOERROR != snmp_status) {
                return snmp_status;
            }
            if (RMON1_ENTRY_UNDER_CREATION != hdr->status &&
                snmp_oid_compare(cloned_body->data_source.objid,
                                 cloned_body->data_source.length,
                                 body->data_source.objid,
                                 body->data_source.length))
                return SNMP_ERR_BADVALUE;
            break;

            break;
        case Leaf_etherStatsOwner:
            if (hdr->new_owner)
                AGFREE(hdr->new_owner);
            hdr->new_owner = AGMALLOC(MAX_OWNERSTRING);
            if (!hdr->new_owner)
                return SNMP_ERR_TOOBIG;
            snmp_status = AGUTIL_get_string_value(var_val, var_val_type,
                                                  var_val_len,
                                                  MAX_OWNERSTRING,
                                                  1, NULL, hdr->new_owner);
            if (SNMP_ERR_NOERROR != snmp_status) {
                return snmp_status;
            }
            break;
        case Leaf_etherStatsStatus:
            snmp_status = AGUTIL_get_int_value(var_val, var_val_type,
                                               var_val_len,
                                               RMON1_ENTRY_VALID,
                                               RMON1_ENTRY_INVALID,
                                               &long_temp);
            if (SNMP_ERR_NOERROR != snmp_status) {
                ag_trace("cannot browse etherStatsStatus");
                return snmp_status;
            }
            hdr->new_status = long_temp;
            break;
            break;
        default:
            ag_trace("%s:unknown leaf_id=%d\n", table_ptr->name,
                     (int) leaf_id);
            return SNMP_ERR_NOSUCHNAME;
        }                       /* of switch by 'leaf_id' */
        break;
    }                           /* of switch by 'action' */

    prev_action = action;
    return SNMP_ERR_NOERROR;
}

/***************************************************
 * Function:var_etherStatsEntry 
 * Purpose: Handles the request for etherStatsEntry variable instances
 ***************************************************/
u_char         *
var_etherStatsEntry(struct variable * vp, oid * name, size_t * length,
                    int exact, size_t * var_len,
                    WriteMethod ** write_method)
{
    static unsigned char zero_octet_string[1];
    static long     long_return;
    static CRTL_ENTRY_T theEntry;
    RMON_ENTRY_T   *hdr;

    *write_method = write_etherStatsEntry;
    hdr = ROWAPI_header_ControlEntry(vp, name, length, exact, var_len,
                                     table_ptr,
                                     &theEntry, sizeof(CRTL_ENTRY_T));
    if (!hdr)
        return NULL;

    if (RMON1_ENTRY_VALID == hdr->status)
        SYSTEM_get_eth_statistics(&theEntry.data_source, &theEntry.eth);

    *var_len = sizeof(long);

    switch (vp->magic) {
    case IDetherStatsIndex:
        long_return = hdr->ctrl_index;
        return (u_char *) & long_return;
    case IDetherStatsDataSource:
        *var_len = sizeof(oid) * theEntry.data_source.length;
        return (unsigned char *) theEntry.data_source.objid;
    case IDetherStatsDropEvents:
        long_return = 0;        /* theEntry.eth.etherStatsDropEvents; */
        return (u_char *) & long_return;
    case IDetherStatsOctets:
        long_return = theEntry.eth.octets;
        return (u_char *) & long_return;
    case IDetherStatsPkts:
        long_return = theEntry.eth.packets;
        return (u_char *) & long_return;
    case IDetherStatsBroadcastPkts:
        long_return = theEntry.eth.bcast_pkts;
        return (u_char *) & long_return;
    case IDetherStatsMulticastPkts:
        long_return = theEntry.eth.mcast_pkts;
        return (u_char *) & long_return;
    case IDetherStatsCRCAlignErrors:
        long_return = theEntry.eth.crc_align;
        return (u_char *) & long_return;
    case IDetherStatsUndersizePkts:
        long_return = theEntry.eth.undersize;
        return (u_char *) & long_return;
    case IDetherStatsOversizePkts:
        long_return = theEntry.eth.oversize;
        return (u_char *) & long_return;
    case IDetherStatsFragments:
        long_return = theEntry.eth.fragments;
        return (u_char *) & long_return;
    case IDetherStatsJabbers:
        long_return = theEntry.eth.jabbers;
        return (u_char *) & long_return;
    case IDetherStatsCollisions:
        long_return = theEntry.eth.collisions;
        return (u_char *) & long_return;
    case IDetherStatsPkts64Octets:
        long_return = theEntry.eth.pkts_64;
        return (u_char *) & long_return;
    case IDetherStatsPkts65to127Octets:
        long_return = theEntry.eth.pkts_65_127;
        return (u_char *) & long_return;
    case IDetherStatsPkts128to255Octets:
        long_return = theEntry.eth.pkts_128_255;
        return (u_char *) & long_return;
    case IDetherStatsPkts256to511Octets:
        long_return = theEntry.eth.pkts_256_511;
        return (u_char *) & long_return;
    case IDetherStatsPkts512to1023Octets:
        long_return = theEntry.eth.pkts_512_1023;
        return (u_char *) & long_return;
    case IDetherStatsPkts1024to1518Octets:
        long_return = theEntry.eth.pkts_1024_1518;
        return (u_char *) & long_return;
    case IDetherStatsOwner:
        if (hdr->owner) {
            *var_len = strlen(hdr->owner);
            return (unsigned char *) hdr->owner;
        } else {
            *var_len = 0;
            return zero_octet_string;
        }
    case IDetherStatsStatus:
        long_return = hdr->status;
        return (u_char *) & long_return;
    default:
        ERROR_MSG("");
    };                          /* of switch by 'vp->magic'  */

    return NULL;
}

#if 1                           /* debug, but may be used for init. TBD: may be token snmpd.conf ? */
int
add_statistics_entry(int ctrl_index, int ifIndex)
{
    int             ierr;

    ierr = ROWAPI_new(table_ptr, ctrl_index);
    switch (ierr) {
    case -1:
        ag_trace("max. number exedes\n");
        break;
    case -2:
        ag_trace("malloc failed");
        break;
    case -3:
        ag_trace("ClbkCreate failed");
        break;
    case 0:
        break;
    default:
        ag_trace("Unknown code %d", ierr);
        break;
    }

    if (!ierr) {
        register RMON_ENTRY_T *eptr = ROWAPI_find(table_ptr, ctrl_index);
        if (!eptr) {
            ag_trace("cannot find it");
            ierr = -4;
        } else {
            CRTL_ENTRY_T   *body = (CRTL_ENTRY_T *) eptr->body;

            body->data_source.objid[body->data_source.length - 1] =
                ifIndex;

            eptr->new_status = RMON1_ENTRY_VALID;
            ierr = ROWAPI_commit(table_ptr, ctrl_index);
            if (ierr) {
                ag_trace("ROWAPI_commit returned %d", ierr);
            }
        }
    }

    return ierr;
}
#endif

/***************************************************
 * define Variables callbacks 
 ***************************************************/
oid             oidstatisticsVariablesOid[] = { 1, 3, 6, 1, 2, 1, 16, 1 };

struct variable7 oidstatisticsVariables[] = {
    {IDetherStatsIndex, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 1}},
    {IDetherStatsDataSource, ASN_OBJECT_ID, NETSNMP_OLDAPI_RWRITE,
     var_etherStatsEntry, 3, {1, 1, 2}},
    {IDetherStatsDropEvents, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 3}},
    {IDetherStatsOctets, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 4}},
    {IDetherStatsPkts, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 5}},
    {IDetherStatsBroadcastPkts, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 6}},
    {IDetherStatsMulticastPkts, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 7}},
    {IDetherStatsCRCAlignErrors, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 8}},
    {IDetherStatsUndersizePkts, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 9}},
    {IDetherStatsOversizePkts, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 10}},
    {IDetherStatsFragments, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 11}},
    {IDetherStatsJabbers, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 12}},
    {IDetherStatsCollisions, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 13}},
    {IDetherStatsPkts64Octets, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 14}},
    {IDetherStatsPkts65to127Octets, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 15}},
    {IDetherStatsPkts128to255Octets, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 16}},
    {IDetherStatsPkts256to511Octets, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 17}},
    {IDetherStatsPkts512to1023Octets, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 18}},
    {IDetherStatsPkts1024to1518Octets, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStatsEntry, 3, {1, 1, 19}},
    {IDetherStatsOwner, ASN_OCTET_STR, NETSNMP_OLDAPI_RWRITE,
     var_etherStatsEntry, 3, {1, 1, 20}},
    {IDetherStatsStatus, ASN_INTEGER, NETSNMP_OLDAPI_RWRITE,
     var_etherStatsEntry, 3, {1, 1, 21}},
    {IDetherStatsDroppedFrames, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_etherStats2Entry, 3, {4, 1, 1}},
    {IDetherStatsCreateTime, ASN_TIMETICKS, NETSNMP_OLDAPI_RONLY,
     var_etherStats2Entry, 3, {4, 1, 2}},
};

/***************************************************
 * Function:init_statistics 
 * Purpose: register statistics objects in the agent 
 ***************************************************/
void
init_statistics(void)
{
    REGISTER_MIB(MIB_DESCR, oidstatisticsVariables, variable7,
                 oidstatisticsVariablesOid);

    ROWAPI_init_table(&StatCtrlTable, MIB_DESCR, 0, &stat_Create, NULL, /* &stat_Clone, */
                      NULL,     /* &stat_Delete, */
                      &stat_Validate,
                      &stat_Activate, &stat_Deactivate, &stat_Copy);

#if 0                           /* debug */
    {
        int             iii;
        for (iii = 1; iii < 6; iii++) {
            add_statistics_entry(iii, iii);
        }

        add_statistics_entry(10, 16);
        add_statistics_entry(12, 11);
    }
#endif
}

/*
 * end of file statistics.c 
 */
