/*
 * eRTOS SNMP Server
 * Copyright (c) 2000, 2001 Erick Engelke
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <rtos.h>
#include "snmp.h"
#include <net.h>

static esnmp_oid *oids = NULL;
crit_x *_snmp_cs = NULL;
void (*_snmp_prewalk)( int op,  SNMP_OBJECT *list ) = NULL;
int (*_snmp_override)(  BYTE *com, int op, SNMP_OBJECT *list ) = NULL;

/* If you need to process multi-MIB PDUs, set global variable snmpd_maxvars
 * to some higher number inside your application, before you start the
 * snmpd thread.  It is used to allocate buffers at thread start time
 */
#define SNMPD_MAXVARS 1
int snmpd_maxvars = SNMPD_MAXVARS;


//#define DEBUG
#ifdef DEBUG
print_oid( SNMP_OBJECT *oid, esnmp_oid *cmp  )
{
    int i;

    if (oid != NULL )
      for ( i = 0 ; i < oid->IdLen ; ++i )
        cprintf("%u.", oid->Id[i] );
    else
      for ( i = 0 ; i < cmp->oidlen ; ++i )
        cprintf("%u.", cmp->oid[i] );

}
#endif

//---------------------------------------------------------------------
// snmp_copyobjectid - copies "1.3.6..." to a reply packet and sets length
void snmp_copyobjectid( DWORD * dest, char *source, UINT *len )
{
    char *t;
    int i = 0;
    char *s, *sstart;

    /* get a working copy */
    s = sstart = kcalloc( strlen( source ) + 1, 1 );
    if ( s == NULL )
      rt_halt("out of memory in SNMPD");
    strcpy( s, source );

    do {
        if (( t = strchr( s , '.' )) != NULL ) *t = 0;
        dest[ i ] = atol( s );
        if ( t != NULL ) *t = '.';  // repair it
        s = t + 1;
        i++;
    } while ( t );
    *len = i;
    kfree( sstart );
}

//---------------------------------------------------------------------
static void copy_oid( SNMP_OBJECT *dest, SNMP_OBJECT *src )
{
    int i;
    for ( i = 0 ; i < src->IdLen ; ++i )
        dest->Id[i] = src->Id[i];
    dest->IdLen = src->IdLen;
}
//---------------------------------------------------------------------
//---------------------------------------------------------------------
static void copy_newoid( SNMP_OBJECT *dest, esnmp_oid *src )
{
    int i;
    for ( i = 0 ; i < src->oidlen; ++i )
        dest->Id[i] = src->oid[i];
    dest->IdLen = src->oidlen;
}
//---------------------------------------------------------------------

void snmp_compile_oids( esnmp_oid *p )
{
    int i;

    cs_new( &_snmp_cs );
    cs_enter( _snmp_cs );
    oids = p;
    while ( p->oid[0] != -1 ) {
        p->oidlen = 0;      /* safety if oid missing its -1 terminator */

        for ( i = 0 ; i < SNMP_OID_SIZE ; ++i ) {
            if ( p->oid[ i ] == -1 ) {
                p->oidlen = i;
                break;
            }
        }
        p++;
    }
    cs_exit( _snmp_cs );
}

/*
 * compare_oid - tell us if src is > (+1), = (0) or < (-1) cmp
 */
int _snmp_compare_oid( SNMP_OBJECT *src, esnmp_oid *cmp )
{
    long res;
    int i;

    for ( i = 0 ; (i < src->IdLen) && (i < cmp->oidlen) ; ++i ) {
        res = src->Id[i] - cmp->oid[i];
        if ( res < 0 ) return( -1 );
        if ( res > 0 ) return( 1 );
        /* = , continue */
    }
    /* got here, means they were equivalent so far */
    res = src->IdLen - cmp->oidlen;
    if ( res == 0 ) return( 0 );
    if ( res < 0 ) return( 1 );
    else return( -1 );
}

//---------------------------------------------------------------------
#pragma argsused
void snmpthread( DWORD dummy )
{
    SNMP_PDU        pdu;
    SNMP_OBJECT     *list;
    unsigned        listLen;
    int             listi;
    BYTE            com[16];
    unsigned        comLen;
    BYTE            *rqsBuffer, *rqs, *rqsSave;
    unsigned        rqsLen;
    int             op;
    static udp_Socket sock;
    int             socklistening;
    int             res;
    int i, j, rc;
    esnmp_oid *cur;
    int             useoverride;

    if ((( list = kcalloc( sizeof( SNMP_OBJECT ), snmpd_maxvars )) == NULL ) ||
        (( rqs = kcalloc( 2048, 1 )) == NULL ))
        rt_halt("SNMPD, insufficient memory to start");

    rqsSave = rqs; // save this buffer's address

    socklistening = 0;
    do {
        if ( socklistening == 0 ) {
            /* udp_open - use remote_IP=0, bind to first incoming IP address */
            if (!udp_open( &sock, 161, 0, -1, NULL ))
                rt_halt("unable to open SNMP server socket");
            socklistening = 1;
        }

        tcp_tick( NULL );

        /* look for input */
        if ( (rqsLen = sock_dataready( &sock )) == 0 ) {
            rt_sleep( 10 );
            continue;   /* no input */
        }

        /* we received an SNMP request, process it */
        sock_fastread( &sock, rqs, 1024 );

        SnmpDec(  rqs, rqsLen, &pdu, com, sizeof(com), &comLen, list, snmpd_maxvars, &listLen);

        op = pdu.Type;

        useoverride = 0;

        /* build a reply packet */
        pdu.Type = SNMP_PDU_RESPONSE;

        pdu.Request.ErrorStatus = 0;
        pdu.Request.ErrorIndex = 0;

        if ( listLen > snmpd_maxvars )
            listLen = snmpd_maxvars;

        for ( listi = 0 ; listi < listLen ; ++listi ) {
            /* search through for our OID */
            res = 0;

            /* serialize access to the table, incase there are different threads */
            cs_enter( _snmp_cs );

            /* user defined pre-walk function */
            if ( _snmp_prewalk != NULL )
                (*_snmp_prewalk)( op, &list[listi] );

            if ( _snmp_override != NULL ) {
                i = (*_snmp_override)( com, op, &list[listi] );
                if ( i == SNMP_NOOVERRIDE ) useoverride = 0;
                else {
                    useoverride = 1;
                    pdu.Request.ErrorStatus = i;
                }

            }

            /* walk through the oid table - if it exists */
            if ( useoverride ) {
                /* user has supplied the packet */
            }
            else {
                if ( oids != NULL ) {
                    for ( cur = oids ;  cur->oid[0] != -1 ; ++cur ) {
                        if ( ( op == SNMP_PDU_GET ) || ( op == SNMP_PDU_SET )) {
                            if ( _snmp_compare_oid( &list[listi] , cur ) == 0 ) {
                                res = 1;
                                break;
                            }
                        }
                        if ( op == SNMP_PDU_NEXT ) {
                            if ( _snmp_compare_oid( &list[listi] , cur ) < 0 ) {
                                res = 1;
                                break;
                            }
                        }
                    }
                }

                if (( res == 0 ) || (cur == NULL)) {
                    /* none found */
                    pdu.Request.ErrorStatus = SNMP_NOSUCHNAME;
                    pdu.Request.ErrorIndex = 0;
                } else {
                    copy_newoid( &list[listi], cur );
                    if ( cur->fn != NULL )
                        pdu.Request.ErrorStatus = (*cur->fn)(
                            &sock, com, op, &list[listi], cur->data1, cur->data2 );
                    else {
                        /* no function, use default formatting */
                        if ( op == SNMP_PDU_SET )
                            pdu.Request.ErrorStatus = SNMP_READONLY;
                        else {
                            list[listi].Type = cur->data1;
                            switch ( cur->data1 ) {
                                case SNMP_DISPLAYSTR :
                                    strcpy( list[listi].Syntax.BufChr, (char *)cur->data2 );
                                    list[listi].SyntaxLen = strlen( (char*)cur->data2 );
                                    break;
                                case SNMP_OBJECTID :
                                    snmp_copyobjectid( list[listi].Syntax.BufInt,
                                        (char *)cur->data2,
                                         &(list[listi].SyntaxLen));
                                    break;
                                default :   /* preserve other types */
                                    list[listi].Syntax.LngInt = cur->data2;
                                    break;
                            }
                        }
                    }
                }
            }
            cs_exit( _snmp_cs );
        }
        rqsLen  = 1024; // 1024 matches the size of the kcalloc() above

        rqs = rqsSave;  // restore the buffer pointer to the start of the buffer
        rc = SnmpEnc(  &rqs, &rqsLen, &pdu, com,  comLen, list, listLen );
        if( rc == 0 ) puts("SnmpEnc failed!");

        sock_fastwrite( &sock, rqs, rqsLen );
        sock_close( &sock );

        /* open next snmp server */
        socklistening = 0;

    } while( 1 );
}

