/**********************************************************************
   Mark Butcher    Bsc (Hons) MPhil MIEE

   M.J.Butcher Consulting
   Obere Bahnhofstrasse 13, 5507 Mellingen

   www.uTasker.com      Skype: M_J_Butcher

   ---------------------------------------------------------------------
   File:        SNMP.c
   Project:     Single Chip Embedded Internet
   ---------------------------------------------------------------------
   Copyright (C) M.J.Butcher Consulting 2004..2007
   *********************************************************************/  



/***********************************************************************/      
/***********************************************************************/ 
/*******                                                        ********/ 
/*******     WARNING: This file is in development !!!!          ********/ 
/*******     It is included in the present service pack         ********/  
/*******     so that you can use it for first tests or          ********/  
/*******     you can further develop it for your own needs      ********/ 
/*******     or in collaboration with myself.                   ********/ 
/*******     To learn more about the state of development       ********/  
/*******     or if you have any questions or suggestions        ********/  
/*******     please email me at M_J_Butcher@IEE.org             ********/  
/***********************************************************************/ 
/***********************************************************************/ 

#include "config.h"


#ifdef USE_SNMP




#define SNMP_EVENT_SUCCESS           1                                   // to be put in tcpip.h

#define SNMP_ERROR_NO_ARP_RES        20
#define SNMP_ERROR_TIMEOUT           21
#define SNMP_ERROR_GENERAL           22
#define SNMP_OPCODE_ERROR            23



#define OWN_TASK          TASK_SNMP

#define SNMP_RESEND_PERIOD (DELAY_LIMIT)(2*SEC)

#define E_SNMP_RESEND      1


#define SNMP_NUM_RETRIES   5


#define SNMP_ERROR_BUSY        -1
#define SNMP_ERROR_LABEL       -2
#define SNMP_ERROR_NAME        -3
#define SNMP_ERROR_OVERFLOW    -4

#define SNMP_BUFFER 100
typedef struct stUDP_SNMP_MESSAGE
{     
    UDP_HEADER     tUDP_Header;                                          // reserve header space
    unsigned char  ucUDP_Message[SNMP_BUFFER];
} UDP_SNMP_MESSAGE;


static unsigned short fnFormatFrame(unsigned char *ucData, unsigned short usLength, unsigned char ucPDU_type);
static int  fnSNMPListner(USOCKET SNMP_socket, unsigned char ucEvent, unsigned char *ucIP, unsigned short usPort, unsigned char *ucData, unsigned short usDataLen);
static int  fnRetry(void);
static void fnSNMP_error(unsigned char ucError);


static USOCKET SNMPSocketNr = -1;                                         // UDP socket number
static unsigned char ucSNMPRetries;
static int (*fnClientListener)(unsigned char ucEvent, unsigned char *data, unsigned short usLength);
static unsigned char ucIP_SNMP_Manager[IPV4_LENGTH] = {0};
static const CHAR *ptrSNMP_host_name;


static const unsigned char ucSNMPV1[] = {ASN1_INTEGER, 0x01, SNMPV1};

// SNMP task
//
void fnSNMP(TTASKTABLE *ptrTaskTable)
{    
    QUEUE_HANDLE PortIDInternal = ptrTaskTable->TaskID;                  // queue ID for task input
    unsigned char ucInputMessage[SMALL_QUEUE];                           // reserve space for receiving messages

    if ( fnRead( PortIDInternal, ucInputMessage, HEADER_LENGTH )) {      // check input queue
        if ( ucInputMessage[ MSG_SOURCE_TASK ] == TASK_ARP) {
            fnRead( PortIDInternal, ucInputMessage, ucInputMessage[MSG_CONTENT_LENGTH]);  // read the contents
            if (ARP_RESOLUTION_FAILED == ucInputMessage[ 0 ]) {
                if (fnRetry()) {                                         // SNMP manager could not be resolved - try a few times before informing listener 
                    fnSNMP_error(SNMP_ERROR_NO_ARP_RES);
                }
            }
            else if (ARP_RESOLUTION_SUCCESS == ucInputMessage[ 0 ]) {
                ucSNMPRetries = 0;                                       // the first request was lost due to ARP resolution
                fnRetry();                                               // the ARP resolution was successful - now start fresh
            }
        }
    }
}

extern int fnStartSNMP(int (*fnAppCallback)(unsigned char ucEvent, unsigned char *data, unsigned short usLength), unsigned char *ucIP)
{
    if (SNMPSocketNr < 0) {                                              // socker doesn't exist
        if (((SNMPSocketNr = fnGetUDP_socket(TOS_MINIMISE_DELAY, fnSNMPListner, (UDP_OPT_SEND_CS | UDP_OPT_CHECK_CS))) >= 0)) {
            fnBindSocket(SNMPSocketNr, SNMP_AGENT_PORT);
        }    
    } 
    fnClientListener = fnAppCallback;
    uMemcpy(ucIP_SNMP_Manager, ucIP, IPV4_LENGTH);
    return SNMPSocketNr;
}

static unsigned short fnAddTimeStamp(unsigned char *ptrBuffer)
{
    unsigned long ulHundredths = uTaskerSystemTick;
    *ptrBuffer++ = ASN1_TIME_STAMP;
    *ptrBuffer++ = sizeof(unsigned long);                                // content length
    ulHundredths *= TICK_RESOLUTION;
    ulHundredths /= 100;                                                 // up time in 100th of second
    *ptrBuffer++ = (unsigned char)(ulHundredths>> 24);
    *ptrBuffer++ = (unsigned char)(ulHundredths>> 16);
    *ptrBuffer++ = (unsigned char)(ulHundredths>> 8);
    *ptrBuffer   = (unsigned char)(ulHundredths);
    return (sizeof(unsigned long) + 2);
}


    /*
    UDP_Message.ucUDP_Message[usLength++] = ASN1_INTEGER;
    UDP_Message.ucUDP_Message[usLength++] = 1;                           // one byte
    UDP_Message.ucUDP_Message[usLength++] = ucSpecificCode;
    uMemcpy(&UDP_Message.ucUDP_Message[usLength], &ucTestTime, sizeof(ucTestTime)); // still dummy
    usLength += sizeof(ucTestTime);
    */

static int fnGenerateTrap(unsigned char ucTrap, unsigned char ucSpecificCode)
{
//  const unsigned char ucTestTime[] = {0x43, 0x04, 0x00, 0x0b, 0x97, 0xf8}; // dummy time stamp 
    const unsigned char ucTestObj[] = {0x2b, 6, 1,2,1,2,1,0};
    unsigned short usLength = 0;
    UDP_SNMP_MESSAGE UDP_Message;
    UDP_Message.ucUDP_Message[0] = ASN1_SEQUENCE;
    UDP_Message.ucUDP_Message[1] = 0;                                    // place holder for length to be added
    uMemcpy(&UDP_Message.ucUDP_Message[2], ucSNMPV1, sizeof(ucSNMPV1));
    usLength = sizeof(ucSNMPV1) + 2;
    UDP_Message.ucUDP_Message[usLength++] = ASN1_OCTET_STRING;
    UDP_Message.ucUDP_Message[usLength] = fnClientListener(SNMP_GET_COMMUNITY, &UDP_Message.ucUDP_Message[usLength+1], 0);
    usLength += UDP_Message.ucUDP_Message[usLength]+1;
    UDP_Message.ucUDP_Message[usLength++] = ASN1_SNMP_TRAP;
    UDP_Message.ucUDP_Message[usLength++] = 0;                           // place holder for length to be added
    UDP_Message.ucUDP_Message[usLength++] = ASN1_OBJECT_IDENT;
    UDP_Message.ucUDP_Message[usLength] = fnClientListener(SNMP_GET_ENTERPRISE, &UDP_Message.ucUDP_Message[usLength+1], 0);
    usLength += UDP_Message.ucUDP_Message[usLength]+1;
    UDP_Message.ucUDP_Message[usLength++] = ASN1_IP_ADDRESS;
    UDP_Message.ucUDP_Message[usLength++] = IPV4_LENGTH;    
    uMemcpy(&UDP_Message.ucUDP_Message[usLength], &network.ucOurIP[0], IPV4_LENGTH);
    usLength += IPV4_LENGTH;
    UDP_Message.ucUDP_Message[usLength++] = ASN1_INTEGER;
    UDP_Message.ucUDP_Message[usLength++] = 1;                           // one byte
    UDP_Message.ucUDP_Message[usLength++] = ucTrap;
    UDP_Message.ucUDP_Message[usLength++] = ASN1_INTEGER;
    UDP_Message.ucUDP_Message[usLength++] = 1;                           // one byte
    UDP_Message.ucUDP_Message[usLength++] = ucSpecificCode;
    usLength += fnAddTimeStamp(&UDP_Message.ucUDP_Message[usLength]);
/*
    uMemcpy(&UDP_Message.ucUDP_Message[usLength], &ucTestTime, sizeof(ucTestTime)); // still dummy
    usLength += sizeof(ucTestTime);
    */
    // No object - NULL
    UDP_Message.ucUDP_Message[usLength++] = ASN1_SEQUENCE;
    UDP_Message.ucUDP_Message[usLength++] = 0;
/* eg. of adding an object
    UDP_Message.ucUDP_Message[usLength++] = ASN1_SEQUENCE;
    UDP_Message.ucUDP_Message[usLength++] = 0;                           // place holder for length to be added
    UDP_Message.ucUDP_Message[usLength++] = ASN1_SEQUENCE;
    UDP_Message.ucUDP_Message[usLength++] = 0;                           // place holder for length to be added
    UDP_Message.ucUDP_Message[usLength++] = ASN1_OBJECT_IDENT;    
    UDP_Message.ucUDP_Message[usLength++] = sizeof(ucTestObj);
    uMemcpy(&UDP_Message.ucUDP_Message[usLength], &ucTestObj, sizeof(ucTestObj)); // still dummy
    usLength += sizeof(ucTestObj);
    UDP_Message.ucUDP_Message[usLength++] = ASN1_INTEGER;
    UDP_Message.ucUDP_Message[usLength++] = 1;                           // one text integer byte
    UDP_Message.ucUDP_Message[usLength++] = 0x21;
    */

    usLength = fnFormatFrame(UDP_Message.ucUDP_Message, usLength, ASN1_SNMP_TRAP);

    if (!usLength) {
        usLength = (UDP_Message.ucUDP_Message[1] + 2);
    }

    return (fnSendUDP(SNMPSocketNr, ucIP_SNMP_Manager, SNMP_MANAGER_PORT, (unsigned char *)&UDP_Message.tUDP_Header, usLength, OWN_TASK) < usLength);
}

typedef struct stTrapList {
    unsigned char ucTrapType;
    unsigned char ucTrapSpecificCode;
}
TRAP_LIST;

#define TRAP_QUEUE_LENGTH 3

TRAP_LIST trap_list[TRAP_QUEUE_LENGTH] = {0};

static unsigned char ucTrapCnt = 0;

static void fnResendSNMPTrap(void)
{
    if (fnGenerateTrap(trap_list[0].ucTrapType, trap_list[0].ucTrapSpecificCode) == 0) {                      
        int iTrapQueue = 1;                                              // frame could be delivered this time so delete it from the queue
        while (--ucTrapCnt != 0) {                                       // any further in queue are also sent now
            fnGenerateTrap(trap_list[iTrapQueue].ucTrapType, trap_list[iTrapQueue].ucTrapSpecificCode);
            iTrapQueue++;
        }
    } 
}

// List of traps waiting to be sent (list is used only when manager is being resolved)
//
static void fnEnterTrap(unsigned char ucTrap, unsigned char ucSpecificCode)
{
    if (ucTrapCnt >= TRAP_QUEUE_LENGTH) {
        return;                                                          // no space left - drop trap message
    }
    trap_list[ucTrapCnt].ucTrapType = ucTrap;
    trap_list[ucTrapCnt].ucTrapSpecificCode = ucSpecificCode;
    ucTrapCnt++;
}

// Resend a filed trap frame - usually due to ARP resolve
//
static int fnRetry(void)
{
    if (ucSNMPRetries < SNMP_NUM_RETRIES) {
        ucSNMPRetries++;
        fnResendSNMPTrap();                                              // try again
    }
    else {                                                               // last timeout - resolution failed
        ucSNMPRetries = 0;
        ucTrapCnt = 0;                                                   // cancel any trap sin teh queue bbecause there is no manager available
        return 1;                                                        // we give up
    }
    return 0;                                                            // OK - repetition attempted
}






static unsigned char *fnGetASN1_length(unsigned char *ucData, unsigned short *usStructureLength)
{
    unsigned short usLength = *ucData++;                                     
    if (usLength & 0x80) {                                               // is length spread over several bytes?
        usLength &= ~0x80;                                               // the number of byte 
        switch (usLength) {
        case 1:
            usLength = *ucData++;
            break;
        case 2:
            usLength = *ucData++;
            usLength <<= 8;
            usLength |= *ucData++;
            break;
        default:                                                         // assume structure length of greater than 64k are invalid
            usLength = 0;
            break;
        }
    }
    *usStructureLength = usLength;
    return ucData;
}

unsigned short fnAddASN1_string(unsigned char *ucData, CHAR *cPtrString)
{
    unsigned short usStringLength = uStrlen(cPtrString);
    unsigned short usContentLength;

    *ucData++ = ASN1_OCTET_STRING;
    if (usStringLength <= 0x7f) {
        *ucData++ = (unsigned char)usStringLength;
        usContentLength = 2;
    }
    else {
        *ucData++ = 0x82;                                                // length represented by 2 bytes
        *ucData++ = (unsigned char)(usStringLength >> 8);
        *ucData++ = (unsigned char)(usStringLength);
        usContentLength = 4;
    }
    uMemcpy(ucData, (unsigned char *)cPtrString, usStringLength);
    return (usContentLength + usStringLength);
}

/*   MIB-II groups
      - System
      - Interfaces
      - Address Translation (deprecated)
      - IP
      - ICMP
      - TCP
      - UDP
      - EGP
      - Transmission
      - SNMP
*/

// Get next identifier
//
static unsigned short fnGetObjectIdentifier(unsigned char *ucData, unsigned char *ucError, unsigned char *ucErrorIndex)
{
//  const unsigned char ucTestTime[] = {0x43, 0x04, 0x00, 0x0b, 0x97, 0xf8};
    unsigned short usNewObjectLength;
    unsigned char *ptrInitialData = ucData;

    do {
        if (*ucData++ != ASN1_SEQUENCE) {
            return 0;
        }
        ucData = fnGetASN1_length(ucData, &usNewObjectLength);
    } while (*ucData == ASN1_SEQUENCE);

    if (ASN1_OBJECT_IDENT != *ucData++) {                                // check that there is an object
        return 0;                                                        // unexpected error
    }
    ucData = fnGetASN1_length(ucData, &usNewObjectLength); 
    if (*ucData++ != 0x2b) {
        return 0;                                                        // not iso.org node
    }    
    usNewObjectLength = 0;
    switch (*ucData++) {
    case 6:                                                              // 1.3.6.....      [iso.org.dod]
        switch (*ucData++) {
        case 1:                                                          // 1.3.6.1....     [iso.org.dod.internet]
            switch (*ucData++) {
            case 2:                                                      // 1.3.6.1.2...    [iso.org.dod.internet.mgmt]
                switch (*ucData++) {
                case 1:                                                  // 1.3.6.1.2.1..   [iso.org.dod.internet.mgmt.mib]
                    switch (*ucData++) {
                    case 1:                                              // 1.3.6.1.2.1.1.  [iso.org.dod.internet.mgmt.mib.system]
                        switch (*ucData++) {
                        case 1:                                          // 1.3.6.1.2.1.1.1 sysDescr                          
                            usNewObjectLength = fnAddASN1_string(++ucData, "Test1");
                            break;
                        case 2:                                          // 1.3.6.1.2.1.1.2 sysObjectID
                            break;
                        case 3:                                          // 1.3.6.1.2.1.1.3 sysUpTime
                            usNewObjectLength = fnAddTimeStamp(++ucData);
                            break;
                        case 4:                                          // 1.3.6.1.2.1.1.4 sysContact
                            usNewObjectLength = fnAddASN1_string(++ucData, "Test3");               
                            break;
                        case 5:                                          // 1.3.6.1.2.1.1.5 sysName
                            usNewObjectLength = fnAddASN1_string(++ucData, "Test2");                
                            break;
                        case 6:                                          // 1.3.6.1.2.1.1.6 sysLocation
                            usNewObjectLength = fnAddASN1_string(++ucData, "Test4");              
                            break;
                        case 7:                                          // 1.3.6.1.2.1.1.7 sysServices
                        default:
                            break;
                        }
                        break;

                    case 2:                                              // 1.3.6.1.2.1.2.  [iso.org.dod.internet.mgmt.mib.interfaces]
                        switch (*ucData++) {
                        default:
                            break;
                        }
                        break;

                  //case 3:                                              // depreciated
                    case 4:                                              // 1.3.6.1.2.1.4.  [iso.org.dod.internet.mgmt.mib.ip]
                        switch (*ucData++) {

                        default:
                            break;
                        }
                        break;

                    case 5:                                              // 1.3.6.1.2.1.5.  [iso.org.dod.internet.mgmt.mib.icmp]
                        switch (*ucData++) {

                        default:
                            break;
                        }
                        break;

                    case 6:                                              // 1.3.6.1.2.1.6.  [iso.org.dod.internet.mgmt.mib.tcp]
                        switch (*ucData++) {

                        default:
                            break;
                        }
                        break;

                    case 7:                                              // 1.3.6.1.2.1.7.  [iso.org.dod.internet.mgmt.mib.udp]
                        switch (*ucData++) {
                        case 1:                                          // 1.3.6.1.2.1.7.1 - in datagrams
                            break;
                        case 2:                                          // 1.3.6.1.2.1.7.2 - no of ports
                            break;
                        case 3:                                          // 1.3.6.1.2.1.7.3 - UDP in errors
                            break;
                        case 4:                                          // 1.3.6.1.2.1.7.4 - UDP out datagrams
                            break;
                        case 5:                                          // 1.3.6.1.2.1.7.5
                            switch (*ucData++) {
                            case 1:                                      // 1.3.6.1.2.1.7.5.1
                                switch (*ucData++) {                     // UDP entry table
                                case 1:                                  // 1.3.6.1.2.1.7.5.1.1 - UDP local address
                                    break;
                                case 2:                                  // 1.3.6.1.2.1.7.5.1.2 - UDP local port
                                    break;
                                }
                                break;
                            default:
                                break;
                            }
                            break;
                        default:
                            break;
                        }
                        break;

                    case 8:                                              // 1.3.6.1.2.1.8.  [iso.org.dod.internet.mgmt.mib.egp]
                        break;

                    case 10:                                             // 1.3.6.1.2.1.10. [iso.org.dod.internet.mgmt.mib.transmission]
                        break;

                    case 11:                                             // 1.3.6.1.2.1.11. [iso.org.dod.internet.mgmt.mib.snmp]
                        break;

                    default:
                        break;
                    }
                default:
                    break;
                }
                break;

            case 3:                                                      // 1.3.6.1.3...    [iso.org.dod.internet.experimental]
                break;

            case 4:                                                      // 1.3.6.1.4...    [iso.org.dod.internet.private]
                switch (*ucData++) {
                case 1:                                                  // 1.3.6.1.4.1.    [iso.org.dod.internet.private.enterprises]
                    break;
                default:
                    break;
                }
                break;

            default:
                break;
            }
        default:
            break;
        }
        break;

    default:
        break;
    }

    if (!usNewObjectLength) {
        *ucError = SNMP_ERROR_STATUS_NO_SUCH_NAME;
    }
    return ((ucData - ptrInitialData) + usNewObjectLength);
}

// Format a frame based to ensure that field lengths are all correct
//
static unsigned short fnFormatFrame(unsigned char *ucData, unsigned short usLength, unsigned char ucPDU_type)
{
    unsigned char *ucFrameStart = ucData;
    unsigned short usFrameLength = usLength;
    unsigned short usObjectLength;
    unsigned char ucType;
    while (usLength--) {
        ucType = *ucData++;
        if ((ucType == ASN1_SEQUENCE) || (ucType == ucPDU_type)) {
            usLength--;
            if (*ucData & 0x80) {                                        // two byte length - we maintain this format
                ucData++;
                *ucData++ = (unsigned char)(usLength >> 8);
                *ucData++ = (unsigned char)(usLength);
                usLength -= 2;
            }
            else {                                                       // single byte length
                if (usLength > 0x7f) {
                    *ucData++ = 0x82;                                    // length as two bytes
                    usLength -= 2;
                    uMemcpy((ucData + 2), ucData, usLength);             // shift contents
                    *ucData++ = (unsigned char)(usLength >> 8);          // add two byte length
                    *ucData++ = (unsigned char)(usLength);
                    usFrameLength += 2;
                    return (fnFormatFrame(ucFrameStart, usFrameLength, ucPDU_type)); // reitterate with new frame and increased length
                }
                else {
                    *ucData++ = (unsigned char)usLength;                 // maintain format
                }
            }
        }
        else {
            fnGetASN1_length(ucData, &usObjectLength);
            if (usObjectLength > 0x7f) {
                ucData += (3 + usObjectLength);
                usLength -= 3;
            }
            else {
                ucData += (1 + usObjectLength);
                usLength--;
            }
            if ((!usObjectLength) || (usObjectLength > usLength)) {
                if (usLength != 0) {
                    return 0;                                            // serious formatting error. Quit to avoid fatal consequences.
                }
            }
            usLength -= usObjectLength;
        }
    }
    return usFrameLength;
}


// This routine presently handles only the request of one object per UDP rx frame. It uses the rx buffer to build the tx frame.
//
static unsigned short fnGetRequest(unsigned char *ucData)
{
    unsigned short usDataLength;
    unsigned short usFrameLength = 12;
    unsigned char *ucDataStart = ucData;
    unsigned char *ptrError = (ucData + 7);
    unsigned char *ptrErrorIndex = (ucData + 10);

    *ucData++ = ASN1_SNMP_GET_RESPONSE;                                  // modify frame to response
    ucData += 11;                                                        // jump ID and errors
    while ((usDataLength = fnGetObjectIdentifier(ucData, ptrError, ptrErrorIndex)) != 0) {
        usFrameLength += usDataLength;
        break;                                                           // support only one get in frame at the moment
    }
    return usFrameLength;
}

static int fnGetNextRequest(unsigned char *ucData, unsigned short usLength)
{
    return 0;
}

static int fnSetRequest(unsigned char *ucData, unsigned short usLength)
{
    return 0;
}




// The SNMP Agent listener function
//
static int fnSNMPListner(USOCKET SNMP_socket, unsigned char ucEvent, unsigned char *ucIP, unsigned short usPort, unsigned char *ucData, unsigned short usDataLen)
{
    if (ucEvent == UDP_EVENT_RXDATA) {
        CHAR *cCommunity;
        unsigned char *frame_data = ucData;
        unsigned short usCommunityLength;
        unsigned short usStructureLength;

        if (*ucData++ != ASN1_SEQUENCE) {
            return 0;                                                    // not ANS.1 BER conform - silently ignore
        }
        ucData = fnGetASN1_length(ucData, &usStructureLength);
        if ((!usStructureLength) || (uMemcmp(ucData, ucSNMPV1, sizeof(ucSNMPV1)))) { // check the SNMP version number
            return 0;                                                    // silently discard since we only support V1
        }
        ucData += sizeof(ucSNMPV1);
        if (*ucData++ != ASN1_OCTET_STRING) {                            // community string is expected 
            return 0;                                                    // silently ignore if string type not found
        }
        ucData = fnGetASN1_length(ucData, &usCommunityLength);
        cCommunity = ucData;
        ucData += usCommunityLength;
        if (fnClientListener(SNMP_COMMUNITY_CHECK, cCommunity, usCommunityLength) != 0) { // check community
            return 0;                                                    // silently disgard when not for our community
        }
        switch (*ucData) {                                               // decision on the PDU type
        case ASN1_SNMP_GET_REQUEST:
            if ((usStructureLength = fnGetRequest(ucData)) != 0) {       // handle get request
                usStructureLength += (ucData - frame_data);
                usStructureLength = fnFormatFrame(frame_data, usStructureLength, ASN1_SNMP_GET_RESPONSE); // ensure formated correctly
                fnSendUDP(SNMPSocketNr, ucIP, usPort, (frame_data - sizeof(UDP_HEADER)), usStructureLength, OWN_TASK); // send response back
            }
            break;

        case ASN1_SNMP_GET_NEXT_REQUEST:
            fnGetNextRequest(ucData, usStructureLength);                 // handle get next request
            break;

        case ASN1_SNMP_SET_REQUEST:
            fnSetRequest(ucData, usStructureLength);                     // handle set request
            break;

        default:
            break;                                                       // unknown type - silently discard
        }
    }
    return 0;
}



// Build and send a trap message to the SNMP manager
//
extern void fnSendSNMPTrap(unsigned char ucTrap, unsigned char ucSpecificCode)
{
    if ((ucTrapCnt != 0) || (fnGenerateTrap(ucTrap, ucSpecificCode))) {  // is manager presently being resolved
        fnEnterTrap(ucTrap, ucSpecificCode);                             // frame could not be delivered on first attempt (usually due to ARP resolution being started) - enter in the trap list in preparation for repeat attempt(s)
    }
}


static void fnSNMP_error(unsigned char ucError)
{
    fnClientListener(ucError, 0, 0);
}


#endif
