//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project / ILMI
//
//File: ilmiprotocol.cpp
//
//Version: $Revision: 1.37 $
//
//State: $State: Exp $
//
//Date: $Date: 1999/03/11 17:01:52 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
//
//Author:
//      Timo Pärnänen
//
//Description:
//      
//
//Copyright:
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//
//Licence:
//
//
//History: 

#include <OB/CORBA.h>

#include "pf/debug.h"
#include "pf/error.h"
#include "pf/tools.h"
#include "pf/fileparser.h"

#include "mgmt/toveswitch.h"
#include "mgmt/mgmtcommand.h"

#include "mib/mibexception.h"
#include "iface/cpcsif/cpcsdownprimitives.h"
#include "protocol/cpcs/cpcsaadapter.h"

#include "ilmiswitchgetcommand.h"
#include "ilmiprotocol.h"
#include "ilmistate.h"

#include "sw/swdefs.h"
#include "sw/swswitch.h"

MessageInt ilmiProtocol :: SNMP_VERSION = MessageInt(MessageInt::version_1);
AsnOcts ilmiProtocol :: DEFAULT_COMMUNITY = AsnOcts("ILMI");

pfUlong ilmiProtocol :: MAX_REQUEST_ID = 0xFFFF;
pfUlong ilmiProtocol :: SNMP_MESSAGE_LENGTH = 484;
pfUlong ilmiProtocol :: ENC_BUFFER_SIZE = 1024;

pfUlong ilmiProtocol :: ILMI_SDU_SIZE = 4096;

pfConduit ilmiProtocol :: createProtocol(const string &portNumber_,
                                         const string &linkNumber_)
{
    pfConduit newProtocol(new ilmiProtocol(portNumber_, linkNumber_));
    return newProtocol;
}

ilmiProtocol :: ilmiProtocol(const string &portNumber_,
                             const string &linkNumber_)
    : pfProtocol(),
      mgmtDelegationBase(),
      mibDestination(),
      _addressRegistration(this),
      _mibTree(),
      _commandMap(),
      _cursor(0),
      _functionPointer(0),
      _getRequestMessage(),
      _getNextRequestMessage(),
      _getResponseMessage(),
      _setRequestMessage(),
      _trapMessage(),
      _ilmiProxy(this),
      _cpcsProxy(),
      _controlPort(0) // DEFAULT CONTROL_PORT 0
{    
    // _udpAddress initialization ??

    string pointCode;
    try
    {
        // Use FileParser to read configFile
        string fileName("ilmi.cfg");
        
        // Create parser and set comment line character
        pfFileParser file(fileName);
        file.setCommentCharacter('#');
        file.parseFile();

        pfUlong debug = 0;
        file.getIntegerValue("DEBUG_TRACE", debug);
        if (debug != 0)
        {
            debugOutputCout();
        }

        //
        // POINT CODE
        //
        if (file.getStringValue("POINT_CODE", pointCode) == false)
        {
            // ++TODO++ cerr & exit are ugly (?)
            cerr << "Mandatory config variable POINT_CODE missing !!" << endl;
            cerr << "Add POINT_CODE to config file";
            exit(0);
        }
        
        // CONTROL PORT
        file.getIntegerValue("CONTROL_PORT", _controlPort);
    }
    catch (pfException &exception)
    {
        // ++TODO++
        debugUser("Config file reading failed");
    }
    
    // GetRequest
    GetRequest_PDU *getRequestPDU = new GetRequest_PDU();
    PDUs *getRequestData = initMessage(_getRequestMessage);
    getRequestData->choiceId = PDUs::get_requestCid;
    getRequestData->get_request = getRequestPDU;
    
    // GetNextRequest
    GetNextRequest_PDU *getNextRequestPDU = new GetNextRequest_PDU();
    PDUs *getNextRequestData = initMessage(_getNextRequestMessage);
    getNextRequestData->choiceId = PDUs::get_next_requestCid;
    getNextRequestData->get_next_request = getNextRequestPDU;

    // GetResponse
    GetResponse_PDU *getResponsePDU = new GetResponse_PDU();
    PDUs *getResponseData = initMessage(_getResponseMessage);
    getResponseData->choiceId = PDUs::get_responseCid;
    getResponseData->get_response = getResponsePDU;

    // SetRequest
    SetRequest_PDU *setRequestPDU = new SetRequest_PDU();
    PDUs *setRequestData = initMessage(_setRequestMessage);
    setRequestData->choiceId = PDUs::set_requestCid;
    setRequestData->set_request = setRequestPDU;

    // Trap
    Trap_PDU *trapPDU = new Trap_PDU();
    PDUs *trapData = initMessage(_trapMessage);
    trapData->choiceId = PDUs::trapCid;
    trapData->trap = trapPDU;
    
    changeState(ilmiState::instance());

    // Create Orbacus CORBA implementation of switchInterface.
    createOB();

    // Construct name for naming component
    string name = pointCode;
    name += "/ports/";
    name += portNumber_;
    name += "/links/";
    name += linkNumber_;
    name += "/ilmi";

    debugUser(name);

    // Register agent to a name serive
    try
    {
        registerToNameService(name);
    }
    catch (...)
    {
        debugUser("registerToNameService failed");
        exit(1);
    }

    // Initialize MIB tree
    initMibTree();

    return;
}

//
//Function: destructor
//
//Description:
//    Delete all five messages and their contents.
//

ilmiProtocol :: ~ilmiProtocol(void)
{
    // GetRequest
    delete _getRequestMessage.data->get_request;
    _getRequestMessage.data->get_request = 0;
    delete _getRequestMessage.data;
    _getRequestMessage.data = 0;

    // GetNextRequest
    delete _getNextRequestMessage.data->get_next_request;
    _getNextRequestMessage.data->get_next_request = 0;
    delete _getNextRequestMessage.data;
    _getNextRequestMessage.data = 0;

    // SetRequest
    delete _setRequestMessage.data->set_request;
    _setRequestMessage.data->set_request = 0;
    delete _setRequestMessage.data;
    _setRequestMessage.data = 0;

    // GetResponse
    delete _getResponseMessage.data->get_response;
    _getResponseMessage.data->get_response = 0;
    delete _getResponseMessage.data;
    _getResponseMessage.data = 0;

    // Trap
    delete _trapMessage.data->trap;
    _trapMessage.data->trap = 0;
    delete _trapMessage.data;
    _trapMessage.data = 0;

    // Command map
    clearCommandMap();

    // CORBA
    unRegisterToNameService();
    return;
}

//
//Function: initIlmiLink
//
//Description:
//    This method opens ILMI AAL connection to a switch and
//    connect ILMI protocol and CPCS adapter together.
//
void ilmiProtocol :: initIlmiLink(string &vpi_, string &vci_)
{
    pfUlong vpi = atoi(vpi_.c_str());
    pfUlong vci = atoi(vci_.c_str());
    debugPfUlong("PORT", _controlPort);
    debugPfUlong("VPI", vpi);
    debugPfUlong("VCI", vci);
    _cpcsProxy = cpcsATMAdapter::createATMConnection(_controlPort,
                                                     vpi,
                                                     vci,
                                                     ILMI_SDU_SIZE);

    _cpcsProxy.connectToA(_ilmiProxy);
    _ilmiProxy.connectToA(_cpcsProxy);
    return;
}

// mgmtDelegationBase interface

//
//Functions: inputs from switch (CORBA)
//
//Description:
//

string ilmiProtocol :: receiveGet(const string &name_)
{
    ilmiCommand *command = ilmiSwitchGetCommand::createSwitchGetCommand(this);
    VarBind *var = command->append();

    try
    {
        _mibTree.stringToVarBind(var, name_);
    }
    catch (mibException &mibexception)
    {
        delete command;
        THROW_INFORMATION_ELEMENT_NON_EXISTENT_OR_NOT_IMPLEMENTED;
    }

    // pfExceptions are let through and they are converted
    // to GeneralErrorException (CORBA UserException) automatically
    // in CORBA implementation.
    sendGetRequest(command);
    
    // Return value is not used here. Execute is used when
    // response is get from a network.
    string null("");
    return null;
}

void ilmiProtocol :: receiveSet(const string &, const string &)
{
    // "setRequest" should never sent by a switch.
    THROW_MESSAGE_TYPE_NON_EXISTENT_OR_NOT_IMPLEMENTED;
    return;
}

void ilmiProtocol :: receiveExecute(mgmtCommand &command_)
{
    string commandName = command_.getCommandName();

    if (commandName.compare("listPrefixes") == 0)
    {
        _addressRegistration.listPrefixes(command_);
    }
    else if (commandName.compare("start") == 0)
    {
        string vpi;
        string vci;
        command_.getNextParameter(vpi);
        command_.getNextParameter(vci);
        try
        {
            initIlmiLink(vpi, vci);
            _addressRegistration.start();
        }
        catch (pfException &ex)
        {
            debugUser("Init failed !!");
            throw toveSwitch_InvalidValue("unspecified");
        }
    }
    else if (commandName.compare("stop") == 0)
    {
        _addressRegistration.stop();
    }
    else
    {
        debugUser("Invalid command");
    }
    return;
}

//
//Functions: mibDestination interface
//
//Description:
//    These methods implements mibDestination interface.
//    Mgmt module is used to get values from a switch.
//

string ilmiProtocol :: getValue(const string &name_)
{
    string result;
    try
    {
        result = sendGet(name_);
    }
    catch (toveSwitch_InvalidVariable &)
    {
        throw mibException(PDUInt::noSuchName);
    }
    return result;
}

void ilmiProtocol :: setValue(const string &, const string &)
{
    // SetRequest is not allowed to go to the switch.
    throw mibException(PDUInt::readOnly);
    return;
}

//
//Function: init methods
//
//Description:
//
//

PDUs *ilmiProtocol :: initMessage(Message &message_) const
{
    message_.version = SNMP_VERSION;
    message_.community = DEFAULT_COMMUNITY;

    PDUs *data = new PDUs();
    message_.data = data;

    return data;
}

void ilmiProtocol :: initMibTree(void)
{
    // ++TODO++ Config Net/User side !!
    _addressRegistration.initialize();

    // ++TODO++ other objects/tables
    return;
}

//
//Function: methods for message attributes
//
//Description:
//
//

bool ilmiProtocol :: verifyVersionNumber(MessageInt version_) const
{
    bool result = 0;
    if (version_ == SNMP_VERSION)
    {
        result = 1;
    }
    return result;
}

bool ilmiProtocol :: communityCheck(AsnOcts community_) const
{
    bool result = 0;
    if (community_ == DEFAULT_COMMUNITY)
    {
        result = 1;
    }
    return result;
}

void ilmiProtocol :: setUDPAddress(const struct sockaddr_in &address_)
{
    _udpAddress = address_;
    return;
}

//
//Function: handler methods for incoming PDUs
//
//Description:
//
//

void ilmiProtocol :: handleGetPDU(AsnInt &requestID_, VarBindList &list_)
{
    debugUser("handleGetPDU");
    
    _functionPointer = getFromMibTree;
    processVarBindList(requestID_, list_);
    return;
}

void ilmiProtocol :: handleGetNextPDU(AsnInt &requestID_, VarBindList &list_)
{
    debugUser("handleGetNextPDU");
    
    _functionPointer = getNextFromMibTree;
    processVarBindList(requestID_, list_);
    return;
}

void ilmiProtocol :: handleSetPDU(AsnInt &requestID_, VarBindList &list_)
{
    debugUser("handleSetPDU");
    
    int i=0;
    try
    {
        list_.SetCurrToFirst();
        for (i=0; i<list_.Count(); i++, list_.GoNext())
        {
            VarBind *var = list_.Curr();
            _mibTree.set(var);
        }
        sendGetResponse(requestID_, PDUInt::noError, 0, list_);
    }
    catch(mibException &mib_exception)
    {
        // ErrorIndex indicates serial number of VarBind
        // where error occured.
        int errorIndex = i+1;
        int errorStatus = mib_exception.getErrorStatus();
        sendGetResponse(requestID_, errorStatus, errorIndex, list_);
    }
    catch (pfException &pf_exception)
    {
        // See comments above.
        int errorIndex = i+1;
        sendGetResponse(requestID_, PDUInt::genErr, errorIndex, list_);
    }
    return;
}

void ilmiProtocol :: handleGetResponsePDU(AsnInt &requestID_,
                                          PDUInt &errorStatus_,
                                          AsnInt &errorIndex_,
                                          VarBindList &list_)
{
    debugUser("handleGetResponsePDU");

    int requestID = (AsnIntType)requestID_;
    mapIterType mapKey = _commandMap.find(requestID_);

    if (mapKey != _commandMap.end())
    {
        try
        {
            ((*mapKey).second)->response(errorStatus_, errorIndex_, list_);
        }
        catch (pfException &exception)
        {
            debugUser("Response failed");
            // Do nothing. A possible exception must be catched
            // here so that command can be removed from the map.
        }
        removeCommand(requestID);
    }
    else
    {
        debugPfUlong("Command not found with requestID", requestID);
    }
    return;
}

void ilmiProtocol :: handleTrapPDU(AsnOid &enterprise_,
                                   NetworkAddress *agent_addr_,
                                   Trap_PDUInt &generic_trap_,
                                   AsnInt &specific_trap_,
                                   TimeTicks &time_stamp_,
                                   VarBindList &list_)
{
    switch ((AsnIntType)generic_trap_)
    {
        case Trap_PDUInt::coldStart :
            _addressRegistration.receiveColdStartTrap(list_);
            break;
        case Trap_PDUInt::warmStart :
        case Trap_PDUInt::linkDown :
        case Trap_PDUInt::linkUp :
        case Trap_PDUInt::authenticationFailure :
        case Trap_PDUInt::egpNeighborLoss :
        case Trap_PDUInt::enterpriseSpecific:
            debugUser("Trap not implemented");
            break;
        default:
            debugUser("Invalid Trap");
            break;
    }
    return;
}

//
//Function: command map handling
//
//Description:
//    Methods to add/remove commands to/from the map.
//

pfUlong ilmiProtocol :: addCommand(ilmiCommand *command_)
{
    pair<mapIterType, bool> p;
 
    // Here is supposed that free place will found beetween range
    // (1 - MAX_REQUEST_ID) so there is no check for rounds.
    do
    {
        _cursor++;
        if (_cursor == MAX_REQUEST_ID)
        {
            _cursor = 1;
        }
        p = _commandMap.insert(mapType::value_type(_cursor, command_));
    }
    while(p.second == 0);
    return _cursor;
}

void ilmiProtocol :: removeCommand(pfUlong requestID_)
{
    _commandMap.erase(requestID_);
    return;
}


void ilmiProtocol :: varBindToString(VarBind *varBind_,
                                     string &sname_,
                                     string &svalue_)
{
    _mibTree.varBindToString(varBind_, sname_, svalue_);
    return;
}

//
//Function: processVarBindList
//
//Description:
//    Method is used to process VarBindList in getRequest
//    or getNextRequest PDU.
//

void ilmiProtocol :: processVarBindList(AsnInt &requestID_, VarBindList &list_)
{
    VarBindList newList;
    int i=0;
    try
    {
        list_.SetCurrToFirst();
        for (i=0; i<list_.Count(); i++, list_.GoNext())
        {
            VarBind *var = list_.Curr();

            // Clone creates a new (empty) VarBind 
            VarBind *newVar = (VarBind *)var->Clone();
            newVar->name = var->name;
            
            (this->*_functionPointer)(newVar);
            newList.AppendCopy(*newVar);
        }
        sendGetResponse(requestID_, PDUInt::noError, 0, newList);
    }
    catch(mibException &mib_exception)
    {
        // ErrorIndex indicates serial number of VarBind
        // where error occured.
        int errorIndex = i+1;
        int errorStatus = mib_exception.getErrorStatus();

        // Old VariableBindingsList with original values
        // (should be EMPTY values) is returned when error occured.
        sendGetResponse(requestID_, errorStatus, errorIndex, list_);
    }
    catch (pfException &pf_exception)
    {
        // See comments above.
        int errorIndex = i+1;
        sendGetResponse(requestID_, PDUInt::genErr, errorIndex, list_);
    }
    return;
}

//
//Functions: get methods for mibtree
//
//Description:
//    These are methods to where function pointer can refer.
//

void ilmiProtocol :: getFromMibTree(VarBind *var_)
{
    _mibTree.get(var_);
    return;
}

void ilmiProtocol :: getNextFromMibTree(VarBind *var_)
{
    _mibTree.getNext(var_);
    return;
}

//
//Functions: send methods
//
//Description:
//    Methods to send SNMP messages to a network.
//

void ilmiProtocol :: sendGetRequest(ilmiCommand *command_)
{
    PDU *pdu = _getRequestMessage.data->get_request;
    sendRequest(command_, pdu, _getRequestMessage);
    return;
}

void ilmiProtocol :: sendGetNextRequest(ilmiCommand *command_)
{
    PDU *pdu = _getNextRequestMessage.data->get_next_request;
    sendRequest(command_, pdu, _getNextRequestMessage);
    return;
}

void ilmiProtocol :: sendSetRequest(ilmiCommand *command_)
{
    PDU *pdu = _setRequestMessage.data->set_request;
    sendRequest(command_, pdu, _setRequestMessage);
    return;
}

void ilmiProtocol :: sendGetResponse(AsnInt &requestID_,
                                     int errorStatus_,
                                     int errorIndex_,
                                     const VarBindList &list_)
{
    PDU *pdu = _getResponseMessage.data->get_response;
    fillPdu(pdu, requestID_, errorStatus_, errorIndex_, list_);

    try
    {
        sendMessage(_getResponseMessage);
    }
    catch (pfException &exception)
    {
        if (exception.getCause() == sigCauseValue_IncorrectMessageLength)
        {
            VarBindList emptyList;
            fillPdu(pdu, requestID_, PDUInt::tooBig, 0, emptyList);
            sendMessage(_getResponseMessage);
        }
    }
    return;
}

void ilmiProtocol :: sendColdStartTrap(const VarBindList &list_)
{
    Trap_PDU *pdu = _trapMessage.data->trap;

    // ++TODO++ Own IP address ???
    IpAddress *ipAddress = new IpAddress("0.0.0.0");
    NetworkAddress *networkAddress = new NetworkAddress;
    networkAddress->choiceId = NetworkAddress::internetCid;
    networkAddress->internet = ipAddress;
    pdu->agent_addr = networkAddress;
    
    pdu->generic_trap = Trap_PDUInt::coldStart;
    pdu->specific_trap = 0;
    pdu->time_stamp = 0; // ++TODO++ sysUpTime ??
    
    pdu->variable_bindings = list_;
    sendMessage(_trapMessage);
    return;
}

//
//Function: sendRequest
//
//Description:
//    Common actions for getRequest, getNextRequest and setRequest
//    message sending.
//

void ilmiProtocol :: sendRequest(ilmiCommand *command_,
                                 PDU *pdu_,
                                 Message &message_)
{
    VarBindList &list = command_->getVarBindList();
    AsnInt requestID = addCommand(command_);

    fillPdu(pdu_, requestID, 0, 0, list);
    
    try
    {
        sendMessage(message_);
    }
    catch (pfException &exception)
    {
        removeCommand((AsnIntType)requestID);
        throw;
    }
    return;
}

//
//Function: sendMessage
//
//Description:
//    This method encodes and send, inside cpcsUNITDATAreq
//    primitive, to sideA any SNMP message.
//

void ilmiProtocol :: sendMessage(Message &message_)
{
    //
    // Encode
    //    
    AsnBuf outputBuf;
    size_t encodedLen;
    int dataSize = ENC_BUFFER_SIZE;
    char dataBuf[ENC_BUFFER_SIZE];
    
    // set up buffer for writing to
    outputBuf.Init(dataBuf, dataSize);
    outputBuf.ResetInWriteRvsMode();

    if (!message_.BEncPdu(outputBuf, encodedLen))
    {
        THROW_INVALID_INFORMATION_ELEMENT_CONTENTS;
    }

    if (encodedLen > SNMP_MESSAGE_LENGTH)
    {
        THROW_INCORRECT_MESSAGE_LENGTH;
    }
    
    //
    // FRAME
    //
    pfByte *encodedData = (pfByte *)outputBuf.DataPtr();
    pfFrame frame;
    frame.putFirst(encodedData, encodedLen);

    //
    // CPCS MESSAGE (TRANSPORTER)
    //
    cpcsUNITDATAreq *messenger = new cpcsUNITDATAreq();
    messenger->setInterfaceData(frame);
    messenger->setAddress(_udpAddress);
    toA(messenger);

    return;
}

//
//Function: fillPDU
//
//Description:
//    This method can be used with GetRequest_PDU, GetNextRequest_PDU,
//    SetRequest_PDU and GetResponse_PDU to set common variables for
//    these PDUs.
//

void ilmiProtocol :: fillPdu(PDU *pdu_,
                             AsnInt &requestID_,
                             int errorStatus_,
                             int errorIndex_,
                             const VarBindList &list_) const
{
    pdu_->request_id = requestID_;
    pdu_->error_status = errorStatus_;
    pdu_->error_index = errorIndex_;
    pdu_->variable_bindings = list_;
    return;
}

void ilmiProtocol :: clearCommandMap(void)
{
    for (mapIterType i = _commandMap.begin(); i != _commandMap.end(); i++)
    {
        delete (*i).second;
    }
    _commandMap.erase(_commandMap.begin(), _commandMap.end());
    return;
}

