

/*
 * seqITcs.hpp
 *
 *  Created on: 14 March, 2023
 *      Author: fulvio, davide
 */

#include "seqISnmp.hpp"
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>


SeqSnmp::SeqSnmp(Ice::PropertiesPtr props)
{

    outletCmdDict["ON"] = "1";
    outletCmdDict["OFF"] = "2";
    outletCmdDict["REBOOT"] = "3";
    outletCmdDict["DELAYED_ON"] = "4";
    outletCmdDict["DELAYED_OFF"] = "5";
    outletCmdDict["DELAYED_REBOOT"] = "6";



    std::string hostname = "Shins.Snmp.Pdu1.Hostname";
    hostnamePdu1 = props->getProperty(hostname);
    if(hostnamePdu1.empty())
    {
        W_LLOG("Could not find property " << hostname);
    }
    hostname = "Shins.Snmp.Pdu2.Hostname";
    hostnamePdu2 = props->getProperty(hostname);
    if(hostnamePdu2.empty())
    {
        W_LLOG("Could not find property " << hostname);
    }

    for(int i = 0; i < 2; i++)
    {
        std::map<std::string, std::string> * pAlias = &aliasPdu1;
        if(i)pAlias = &aliasPdu2;
        for(int j = 1; j <= 8; j++)
        {
            std::stringstream property;
            std::stringstream J;
            J << j;
            property << "Shins.Snmp.Pdu" << i + 1 << ".Outlet" << j;
            std::string property_value = props->getProperty(property.str());
            if(property_value.empty())
            {
                W_LLOG("Could not find property \"" << property.str() << "\"");
            }
            else
            {
                (*pAlias)[property_value] = J.str();
                m_Outlets.push_back(property_value);
            }
        }
    }

    CommonOidPath = props->getProperty("Shins.Snmp.CommonOidPath");
    if(CommonOidPath.empty())
    {
        W_LLOG("Could not find property CommonOidPath");
    }
    ControlPath = props->getProperty("Shins.Snmp.ControlPath");
    if(ControlPath.empty())
    {
        W_LLOG("Could not find property ControlPath");
    }
    StatusPath = props->getProperty("Shins.Snmp.StatusPath");
    if(StatusPath.empty())
    {
        W_LLOG("Could not find property StatusPath");
    }

    // Initialize the SNMP library
    init_snmp("snmpshins");
}

SeqSnmp::~SeqSnmp()
{
}


int SeqSnmp::Setup(const std::map<std::string, std::string> & setup_pdu)
{
    int result = EXIT_SUCCESS;
    std::map<std::string, std::string>::const_iterator itSetup;
    for(itSetup = setup_pdu.begin(); itSetup != setup_pdu.end(); itSetup++)
    {
        if(aliasPdu1.find(itSetup->first) != aliasPdu1.end())
        {
            int SetResult = EXIT_FAILURE;
            int counter = 0;
            while(SetResult == EXIT_FAILURE && counter < 3)
            {
                if(counter > 0)
                {
                    I_LLOG("Retrying snmp setup for " << itSetup->first << "; status : " << itSetup->second);
                }
                SetResult = SetPdu(hostnamePdu1, aliasPdu1.find(itSetup->first)->second, itSetup->second);
                counter++;
                if(SetResult == EXIT_FAILURE && counter < 3)usleep(500000);
                else if(SetResult == EXIT_FAILURE && counter == 3)
                {
                    E_LLOG("Could not set " << itSetup->first << "to status " << itSetup->second << "after three tries");
                    result = EXIT_FAILURE;
                }
            }
        }
        else if(aliasPdu2.find(itSetup->first) != aliasPdu2.end())
        {
            int SetResult = EXIT_FAILURE;
            int counter = 0;
            while(SetResult == EXIT_FAILURE && counter < 3)
            {
                if(counter > 0)
                {
                    I_LLOG("Retrying snmp setup for " << itSetup->first << "; status : " << itSetup->second);
                }
                SetResult = SetPdu(hostnamePdu2, aliasPdu2.find(itSetup->first)->second, itSetup->second);
                counter++;
                if(SetResult == EXIT_FAILURE && counter < 3)usleep(500000);
                else if(SetResult == EXIT_FAILURE && counter == 3)
                {
                    E_LLOG("Could not set " << itSetup->first << "to status " << itSetup->second << "after three tries");
                    result = EXIT_FAILURE;
                }
            }
        }
    }
    return result;
}

int SeqSnmp::SetPdu(std::string hostname, std::string device, std::string set_on)
{
    int result = EXIT_SUCCESS;

    if(hostname.empty() || CommonOidPath.empty() || ControlPath.empty())
    {
        if(hostname.empty())
        {
            E_LLOG("Could not complete operation missing Hostname");
        }
        if(CommonOidPath.empty())
        {
            E_LLOG("Could not complete operation missing CommonOidPath");
        }
        if(ControlPath.empty())
        {
            E_LLOG("Could not complete operation missing ControlPath");
        }
        return EXIT_FAILURE;
    }

    netsnmp_session session, * ss;
    netsnmp_pdu * pdu;
    netsnmp_pdu * response = NULL;

    oid anOID[MAX_OID_LEN];
    size_t anOID_len = MAX_OID_LEN;;

    int status;
//    int count = 1;


    snmp_sess_init(&session);
    session.peername = strdup(hostname.c_str());
    session.securityName = strdup("shins");
    session.version = SNMP_VERSION_3;
    session.securityNameLen = strlen(session.securityName);

    // Open the session
    SOCK_STARTUP;

    ss = snmp_open(&session); // establish the session 

    if(!ss)
    {
        snmp_sess_perror("ack", &session);
        SOCK_CLEANUP;
        E_LLOG("ERROR opening SNMP session for device " << device)
        return EXIT_FAILURE;
    }

    // 1: ON, 2: OFF   
    int value; 
    if(set_on == "ON")value = 1;
    else if(set_on == "OFF")value = 2;
    else
    {
        snmp_sess_perror("ack", &session);
        SOCK_CLEANUP;
        E_LLOG("ERROR: undefined value for device " << device);
        return EXIT_FAILURE;
    }

    // Compose the address using CONTROL
    std::string address = CommonOidPath + ControlPath + device;
    
    // Create the PDU for the data for our request.
    pdu = snmp_pdu_create(SNMP_MSG_SET);
    
    if(!snmp_parse_oid(address.c_str(), anOID, &anOID_len))
    {
        snmp_perror(address.c_str());
        SOCK_CLEANUP;
        E_LLOG("ERROR: invalid addres for device " << device << "; address : " << address);
        return EXIT_FAILURE;
    }

    // Add the value to the PDU
    snmp_pdu_add_variable(pdu, anOID, anOID_len, ASN_INTEGER, ( u_char *)&value, sizeof(value));

    // Send the Request out.
    status = snmp_synch_response(ss, pdu, &response);

    // Process the response.
    if(status == STAT_SUCCESS && response && response->errstat == SNMP_ERR_NOERROR)
    {
        // printf("Outlet status set successfully\n");
        ///////////////////////////////////////////////////////////////////////////////////////        
    }
    else
    {
        // FAILURE: print what went wrong!
        result = EXIT_FAILURE;

        if(status == STAT_SUCCESS)
        {
            if(response)
            {
                E_LLOG("Error in packet : " << snmp_errstring(response->errstat));
            }
            else
            {
                E_LLOG("Error in packet : could not create netsnmp_pdu object for request : " << device <<  " status : " << set_on); 
            }
        }
        else if(status == STAT_TIMEOUT)
        {
            E_LLOG("Timeout : No responce from " << session.peername);
        }
        else
        {
            E_LLOG("Unknown Error");
            snmp_sess_perror("snmpshins", ss);
        }

    }

    // Clean up:
    //  1) free the response.
    //  2) close the session.
    if(response)
    {
        snmp_free_pdu(response);
    }

    snmp_close(ss);

    SOCK_CLEANUP;

    return result;
}


int SeqSnmp::GetPdu(const std::string & device, std::string & set_on)
{
    int result = EXIT_SUCCESS;

    std::string hostname, snmp_device;
    if(aliasPdu1.find(device) != aliasPdu1.end())
    {
        hostname = hostnamePdu1;
        snmp_device = aliasPdu1[device];
    }
    else if(aliasPdu2.find(device) != aliasPdu2.end())
    {
        hostname = hostnamePdu2;
        snmp_device = aliasPdu2[device];
    }
    else
    {
        throw(std::invalid_argument("Unknown snmp device"));
    }

    if(hostname.empty() || CommonOidPath.empty() || StatusPath.empty())
    {
        if(hostname.empty())
        {
            E_LLOG("Could not complete operation missing Hostname");
        }
        if(CommonOidPath.empty())
        {
            E_LLOG("Could not complete operation missing CommonOidPath");
        }
        if(StatusPath.empty())
        {
            E_LLOG("Could not complete operation missing StatusPath");
        }
        return EXIT_FAILURE;
    }

    netsnmp_session session, * ss;
    netsnmp_pdu * pdu;
    netsnmp_pdu * response;

    oid anOID[MAX_OID_LEN];
    size_t anOID_len = MAX_OID_LEN;;

    netsnmp_variable_list * vars;
    int status;

    snmp_sess_init(&session);
    session.peername = strdup(hostname.c_str());
    session.securityName = strdup("shins");
    session.version = SNMP_VERSION_3;
    session.securityNameLen = strlen(session.securityName);

    // Open the session
    SOCK_STARTUP;

    ss = snmp_open(&session); // establish the session 

    if(!ss)
    {
        snmp_sess_perror("ack", &session);
        SOCK_CLEANUP;
        E_LLOG("ERROR opening SNMP session for device " << device)
        return result;
    }

    // Compose the address using CONTROL
    std::string address = CommonOidPath + StatusPath + snmp_device;
    
    // Create the PDU for the data for our request.
    pdu = snmp_pdu_create(SNMP_MSG_GET);
    
    if (!snmp_parse_oid(address.c_str(), anOID, &anOID_len))
    {
        snmp_perror(address.c_str());
        SOCK_CLEANUP;
        E_LLOG("ERROR: invalid address for device " << device << "; address : " << address);
        return result;
    }

    // Add the value to the PDU
    snmp_add_null_var(pdu, anOID, anOID_len);

    // Send the Request out.
    status = snmp_synch_response(ss, pdu, &response);

    // Process the response.
    if (status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR)
    {
        for(vars = response->variables; vars; vars = vars->next_variable)
        {
            if(vars->type == ASN_OCTET_STR)
            {
                char * pBuffer = (char *)malloc(1 + vars->val_len);
                memcpy(pBuffer, vars->val.string, vars->val_len);
                *(pBuffer + vars->val_len) = 0;
                U9_LLOG("Name : " << vars->name << "; length : " << vars->name_length << "; var : " << pBuffer);
                free(pBuffer);
            }
            else if(vars->type == ASN_BIT8)
            {
                U9_LLOG("Name : " << vars->name << "; length : " << vars->name_length << "BIT8");
            }
            else if(vars->type == ASN_INTEGER)
            {
                set_on = "UNDEF";
                long value = *(vars->val.integer);
                if(value == 2)set_on = "OFF";
                else if(value == 1)set_on = "ON";
                //   U9_LLOG("Name : " << vars->name << "; length : " << vars->name_length << "; Value : " << set_on);
            }
            else
            {
                U9_LLOG("Name : " << vars->name << "; length : " << vars->name_length << "; type : " << vars->type);
            }
        }
    }
    else
    {
        // FAILURE: print what went wrong!

        if(status == STAT_SUCCESS)
        {
            E_LLOG("Error in packet : " << snmp_errstring(response->errstat));
        }
        else if(status == STAT_TIMEOUT)
        {
            E_LLOG("Timeout : No responce from " << session.peername);
        }
        else
        {
            E_LLOG("Unknown Error");
            snmp_sess_perror("snmpshins", ss);
        }

    }

    // Clean up:
    //  1) free the response.
    //  2) close the session.
    if (response)
    {
        snmp_free_pdu(response);
    }

    snmp_close(ss);

    SOCK_CLEANUP;

    return EXIT_SUCCESS;
}


int SeqSnmp::getJsonStatus(std::string & jsonStatus)
{
   std::map<std::string, std::string> * pAliasPdu = &aliasPdu1;
   for(int thisPdu = 1; thisPdu <= 2 ; thisPdu++)
   {
       std::stringstream ob_context;
       if(thisPdu == 2) pAliasPdu = &aliasPdu2;
       int outletCounter = 1;
       std::string on_off = ""; // "ON" or "OFF"
       for(std::map<std::string, std::string>::iterator itPdu = pAliasPdu->begin(); itPdu != pAliasPdu->end(); itPdu++)
       {
           int status = GetPdu(itPdu->first , on_off);
           ob_context << "\"" << itPdu->first << "\": {\"name\": \"" << itPdu->first << "\", \"named\":\"" << on_off << "\", \"PDU\": \"" << thisPdu << "\", \"outlet\":\"" << itPdu->second << "\"}";
           if(thisPdu != 2 || outletCounter != 8) ob_context << ", ";
           outletCounter++;
       }
       jsonStatus += ob_context.str();
       //U9_LLOG(ob_context.str());
   }
   return EXIT_SUCCESS;
}
