
//OpenSCADA module DAQ.SNMP file: snmp.cpp
/***************************************************************************
 *   Copyright (C) 2006-2026 by Roman Savochenko, <roman@oscada.org>       *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; version 2 of the License.               *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <sys/times.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>

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

#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION

#include <terror.h>
#include <tsys.h>
#include <tmess.h>
#include <ttypeparam.h>
#include <tdaqs.h>

#include "snmp_client.h"

//*************************************************
//* Modul info!                                   *
#define MOD_ID		"SNMP"
#define MOD_NAME	trS("SNMP client")
#define MOD_TYPE	SDAQ_ID
#define VER_TYPE	SDAQ_VER
#define MOD_VER		"0.9.21"
#define AUTHORS		trS("Roman Savochenko")
#define DESCRIPTION	trS("Provides an implementation of the client of SNMP-service.")
#define LICENSE		"GPL2"
//*************************************************

SNMP_DAQ::TTpContr *SNMP_DAQ::mod;  //Pointer for direct access to the module

extern "C"
{
    TModule::SAt module( int n_mod )
    {
	if(n_mod == 0)	return TModule::SAt(MOD_ID,MOD_TYPE,VER_TYPE);
	return TModule::SAt("");
    }

    TModule *attach( const TModule::SAt &AtMod, const string &source )
    {
	if(AtMod == TModule::SAt(MOD_ID,MOD_TYPE,VER_TYPE)) return new SNMP_DAQ::TTpContr(source);
	return NULL;
    }
}

using namespace SNMP_DAQ;

//*************************************************
//* TTpContr                                      *
//*************************************************
TTpContr::TTpContr( string name ) : TTypeDAQ(MOD_ID)
{
    mod = this;

    modInfoMainSet(MOD_NAME, MOD_TYPE, MOD_VER, AUTHORS, DESCRIPTION, LICENSE, name);

    //Once init of Net-SNMP
    init_snmp("OpenSCADA SNMP client");
}

TTpContr::~TTpContr( )	{ }

void TTpContr::load_( )
{
    //Load parameters from command line

}

void TTpContr::postEnable( int flag )
{
    TTypeDAQ::postEnable(flag);

    //Controler's bd structure
    fldAdd(new TFld("PRM_BD",trS("Table of parameters"),TFld::String,TFld::NoFlag,"30",""));	//????[v1.0] Remove
    fldAdd(new TFld("SCHEDULE",trS("Acquisition schedule"),TFld::String,TFld::NoFlag,"100","1"));
    fldAdd(new TFld("PRIOR",trS("Priority of the acquisition task"),TFld::Integer,TFld::NoFlag,"2","0","-1;199"));
    fldAdd(new TFld("ADDR",trS("Remote host address"),TFld::String,TFld::NoFlag,"30","localhost"));
    fldAdd(new TFld("RETR",trS("Number of retries"),TFld::Integer,TFld::NoFlag,"1","1","0;10"));
    fldAdd(new TFld("TM",trS("Responds timeout, seconds"),TFld::Integer,TFld::NoFlag,"1","3","1;10"));
    fldAdd(new TFld("VER",trS("SNMP version"),TFld::String,TFld::Selectable,"2","1","1;2c;2u;3","SNMPv1;SNMPv2c;SNMPv2u;SNMPv3"));
    fldAdd(new TFld("COMM",trS("Server community/user"),TFld::String,TFld::NoFlag,"20","public"));
    fldAdd(new TFld("V3",trS("V3 parameters"),TFld::String,TFld::NoFlag,"50","authNoPriv:MD5::DES:"));
    fldAdd(new TFld("PATTR_LIM",trS("Limit of the attributes number"),TFld::Integer,TFld::NoFlag,"3","100","10;10000"));

    //Parameter type bd structure
    int t_prm = tpParmAdd("Prm", "PRM_BD", _("Standard"), true);
    tpPrmAt(t_prm).fldAdd(new TFld("OID_LS",trS("OID list (next line separated)"),TFld::String,TFld::FullText|TCfg::NoVal,"100000",""));
}

TController *TTpContr::ContrAttach( const string &name, const string &daq_db )	{ return new TMdContr(name, daq_db, this); }

//*************************************************
//* TMdContr                                      *
//*************************************************
TMdContr::TMdContr(string name_c, const string &daq_db, TElem *cfgelem) :
    TController(name_c,daq_db,cfgelem), enRes(true),
    mPrior(cfg("PRIOR").getId()), mPattrLim(cfg("PATTR_LIM").getId()), mRetr(cfg("RETR").getId()), mTm(cfg("TM").getId()),
    mPer(1e9), prcSt(false), callSt(false), endrunReq(false), prmEnErr(false), tmGath(0), acqErr(dataRes())
{
    //????[v1.0] Remove
    cfg("PRM_BD").setS("SNMPPrm_"+name_c);
}

TMdContr::~TMdContr( )
{
    if(startStat()) stop();
}

string TMdContr::getStatus( )
{
    string rez = TController::getStatus();
    if(startStat() && !redntUse()) {
	if(!acqErr.getVal().empty())	rez = acqErr.getVal();
	else {
	    if(callSt)	rez += TSYS::strMess(_("Acquisition. "));
	    if(period())rez += TSYS::strMess(_("Acquisition with the period %s. "),tm2s(1e-9*period()).c_str());
	    else rez += TSYS::strMess(_("Next acquisition by the cron '%s'. "),atm2s(TSYS::cron(cron()),"%d-%m-%Y %R").c_str());
	    rez += TSYS::strMess(_("Spent time %s."),tm2s(1e-6*tmGath).c_str());
	}
    }

    return rez;
}

struct snmp_session *TMdContr::getSess( )
{
    //Session init
    snmp_sess_init(&session);
    session.version = SNMP_VERSION_1;
    if(ver() == "1")		session.version = SNMP_VERSION_1;
    else if(ver() == "2c")	session.version = SNMP_VERSION_2c;
    else if(ver() == "2u")	session.version = SNMP_VERSION_2u;
    else if(ver() == "3")	session.version = SNMP_VERSION_3;
    wAddr = TSYS::strParse(cfg("ADDR").getS(), 0, ":");
    session.peername = (char *)wAddr.c_str();
    session.retries = mRetr;
    session.timeout = mTm*1000000;
    if(session.version != SNMP_VERSION_3) {
	wComm = cfg("COMM").getS();
	session.community = (u_char*)wComm.c_str();
	session.community_len = wComm.size();
    }
    else {
	wComm = cfg("COMM").getS();
	session.securityName = (char*)wComm.c_str();
	session.securityNameLen = strlen(session.securityName);

	session.securityLevel = SNMP_SEC_LEVEL_NOAUTH;
	if(secLev() == "authNoPriv")	session.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
	else if(secLev() == "authPriv")	session.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV;

	if(session.securityLevel != SNMP_SEC_LEVEL_NOAUTH) {
	    if(secAuthProto() == "SHA") {
		session.securityAuthProto = usmHMACSHA1AuthProtocol;
		session.securityAuthProtoLen = sizeof(usmHMACSHA1AuthProtocol)/sizeof(oid);
	    }
	    else {
		session.securityAuthProto = usmHMACMD5AuthProtocol;
		session.securityAuthProtoLen = sizeof(usmHMACMD5AuthProtocol)/sizeof(oid);
	    }
	    session.securityAuthKeyLen = USM_AUTH_KU_LEN;

	    string w_sauth = secAuthPass();
	    if(generate_Ku(session.securityAuthProto, session.securityAuthProtoLen, (u_char*)w_sauth.data(), w_sauth.size(),
		    session.securityAuthKey, &session.securityAuthKeyLen) != SNMPERR_SUCCESS)
		throw TError(nodePath(), _("Error generating Ku from authentication pass phrase."));
	}

	if(session.securityLevel == SNMP_SEC_LEVEL_AUTHPRIV) {
#ifdef USM_PRIV_PROTO_AES_LEN
	    if(secPrivProto() == "AES") {
		session.securityPrivProto = usmAESPrivProtocol;
		session.securityPrivProtoLen = sizeof(usmAESPrivProtocol)/sizeof(oid);
	    }
	    else
#endif
	    {
		session.securityPrivProto = usmDESPrivProtocol;
		session.securityPrivProtoLen = sizeof(usmDESPrivProtocol)/sizeof(oid);
	    }
	    session.securityPrivKeyLen = USM_PRIV_KU_LEN;

	    string w_spriv = secPrivPass();
	    if(generate_Ku(session.securityPrivProto, session.securityPrivProtoLen, (u_char*)w_spriv.data(), w_spriv.size(),
		    session.securityPrivKey, &session.securityPrivKeyLen) != SNMPERR_SUCCESS)
		throw TError(nodePath(), _("Error generating Ku from privacy pass phrase."));
	}
    }

    return &session;
}

string TMdContr::secLev( )	{ return TSYS::strParse(cfg("V3").getS(), 0, ":"); }

void TMdContr::setSecLev( const string &vl )
{
    cfg("V3").setS(vl+":"+secAuthProto()+":"+secAuthPass()+":"+secPrivProto()+":"+secPrivPass());
}

string TMdContr::secAuthProto( ){ return TSYS::strParse(cfg("V3").getS(), 1, ":"); }

void TMdContr::setSecAuthProto( const string &vl )
{
    cfg("V3").setS(secLev()+":"+vl+":"+secAuthPass()+":"+secPrivProto()+":"+secPrivPass());
}

string TMdContr::secAuthPass( )	{ return TSYS::strParse(cfg("V3").getS(), 2, ":"); }

void TMdContr::setSecAuthPass( const string &vl )
{
    cfg("V3").setS(secLev()+":"+secAuthProto()+":"+vl+":"+secPrivProto()+":"+secPrivPass());
}

string TMdContr::secPrivProto( ){ return TSYS::strParse(cfg("V3").getS(), 3, ":"); }

void TMdContr::setSecPrivProto( const string &vl )
{
    cfg("V3").setS(secLev()+":"+secAuthProto()+":"+secAuthPass()+":"+vl+":"+secPrivPass());
}

string TMdContr::secPrivPass( )	{ return TSYS::strParse(cfg("V3").getS(), 4, ":"); }

void TMdContr::setSecPrivPass( const string &vl )
{
    cfg("V3").setS(secLev()+":"+secAuthProto()+":"+secAuthPass()+":"+secPrivProto()+":"+vl);
}

TParamContr *TMdContr::ParamAttach( const string &name, int type ) { return new TMdPrm(name,&owner().tpPrmAt(type)); }

void TMdContr::enable_( )	{ prmEnErr = false; }

void TMdContr::start_( )
{
    getSess();

    //Start the gathering data task
    if(!prcSt) SYS->taskCreate(nodePath('.',true), mPrior, TMdContr::Task, this);
}

void TMdContr::stop_( )
{
    //Stop the request and calc data task
    if(prcSt) SYS->taskDestroy(nodePath('.',true), &endrunReq);
}

void TMdContr::prmEn( TMdPrm *prm, bool val )
{
    unsigned iPrm;

    MtxAlloc res(enRes, true);
    for(iPrm = 0; iPrm < pHd.size(); iPrm++)
	if(&pHd[iPrm].at() == prm) break;

    if(val && iPrm >= pHd.size()) pHd.push_back(prm);
    if(!val && iPrm < pHd.size()) pHd.erase(pHd.begin()+iPrm);
}

void *TMdContr::Task( void *icntr )
{
    TMdContr &cntr = *(TMdContr *)icntr;

    string	daqerr;

    //Start SNMP-net session
    void *ss =  snmp_sess_open(cntr.getSess());
    if(!ss) { mess_err(mod->nodePath().c_str(), "%s", _("Error opening SNMP session.")); return NULL; }

    cntr.endrunReq = false;
    cntr.prcSt = true;

    while(!cntr.endrunReq) {
	cntr.callSt = true;
	int64_t t_cnt = TSYS::curTime();

	// Update controller's data
	daqerr.clear();
	MtxAlloc res(cntr.enRes, true);
	for(unsigned iP = 0; iP < cntr.pHd.size() && !cntr.redntUse(); iP++)
	    try { cntr.pHd[iP].at().upVal(ss); }
	    catch(TError &err) { daqerr = err.mess; }
	res.unlock();
	cntr.tmGath = TSYS::curTime()-t_cnt;
	cntr.callSt = false;

	cntr.acqErr.setVal(daqerr);

	TSYS::taskSleep(cntr.period(), cntr.period() ? "" : cntr.cron());
    }

    snmp_sess_close(ss);

    cntr.prcSt = false;

    return NULL;
}

string TMdContr::oid2str( oid *ioid, size_t isz, const string &sep )
{
    string rez;
    for(unsigned i_el = 0; i_el < isz; i_el++)
	rez += sep+i2s(ioid[i_el]);

    return rez;
}

void TMdContr::str2oid( const string &str, oid *ioid, size_t &isz, const string &sep )
{
    string sb;
    unsigned n = 0;
    for(int off = 0; ((sb=TSYS::strParse(str,0,sep,&off)).size() || off < (int)str.size()) && n < isz; )
	if(sb.size()) ioid[n++] = s2i(sb);
    isz = n;
}

void TMdContr::cntrCmdProc( XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	TController::cntrCmdProc(opt);
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/SCHEDULE",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR), "tp","str",
	    "dest","sel_ed", "sel_list",TMess::labSecCRONsel().c_str(), "help",TMess::labSecCRON().c_str());
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/PRIOR",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR), "help",TMess::labTaskPrior().c_str());
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/ADDR",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR), "tp","str",
	    "help",_("SNMP agent host in IP address or domain host name.\nAlso you can set port like \"localhost:161\""));
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/TM",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR));
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/VER",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR));
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/COMM",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR), "tp","str",
	    "help",_("Community group or user."));
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/PATTR_LIM",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR));
	ctrRemoveNode(opt,"/cntr/cfg/V3");
	if(ver() == "3") {
	    ctrMkNode3("fld",opt,-1,"/cntr/cfg/SecLev",_("Security level"),SEC_RD|(startStat()?0:SEC_WR),
		"tp","str", "idm","1", "dest","select",
		"sel_id","noAurhNoPriv;authNoPriv;authPriv", "sel_list",_("No auth/No privacy;Auth/No privacy;Auth/Privacy"));
	    if(secLev() != "noAurhNoPriv") {
		ctrMkNode3("fld",opt,-1,"/cntr/cfg/AuthProto",_("Auth"),SEC_RD|(startStat()?0:SEC_WR), "tp","str", "dest","select", "sel_list","MD5;SHA");
		ctrMkNode3("fld",opt,-1,"/cntr/cfg/AuthPass","",SEC_RD|(startStat()?0:SEC_WR), "tp","str");
	    }
	    if(secLev() == "authPriv") {
#ifdef USM_PRIV_PROTO_AES_LEN
		const char *prtLs = "DES;AES";
#else
		const char *prtLs = "DES";
#endif
		ctrMkNode3("fld",opt,-1,"/cntr/cfg/PrivProto",_("Privacy"),SEC_RD|(startStat()?0:SEC_WR), "tp","str", "dest","select", "sel_list",prtLs);
		ctrMkNode3("fld",opt,-1,"/cntr/cfg/PrivPass","",SEC_RD|(startStat()?0:SEC_WR), "tp","str");
	    }
	}
        return;
    }

    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/cntr/cfg/SecLev") {
	if(ctrChkNode2(opt,"get",SEC_RD)) opt->setText(secLev());
	if(ctrChkNode2(opt,"set",SEC_WR)) setSecLev(opt->text());
    }
    else if(a_path == "/cntr/cfg/AuthProto") {
	if(ctrChkNode2(opt,"get",SEC_RD)) opt->setText(secAuthProto());
	if(ctrChkNode2(opt,"set",SEC_WR)) setSecAuthProto(opt->text());
    }
    else if(a_path == "/cntr/cfg/AuthPass") {
	if(ctrChkNode2(opt,"get",SEC_RD)) opt->setText(string(secAuthPass().size(),'*'));
	if(ctrChkNode2(opt,"set",SEC_WR)) setSecAuthPass(opt->text());
    }
    else if(a_path == "/cntr/cfg/PrivProto") {
	if(ctrChkNode2(opt,"get",SEC_RD)) opt->setText(secPrivProto());
	if(ctrChkNode2(opt,"set",SEC_WR)) setSecPrivProto(opt->text());
    }
    else if(a_path == "/cntr/cfg/PrivPass") {
	if(ctrChkNode2(opt,"get",SEC_RD)) opt->setText(string(secPrivPass().size(),'*'));
	if(ctrChkNode2(opt,"set",SEC_WR)) setSecPrivPass(opt->text());
    }
    else TController::cntrCmdProc(opt);
}

bool TMdContr::cfgChange( TCfg &co, const TVariant &pc )
{
    TController::cfgChange(co, pc);

    if(co.fld().name() == "SCHEDULE")
	mPer = TSYS::strSepParse(cron(), 1, ' ').empty() ? vmax(0,(int64_t)(1e9*s2r(cron()))) : 0;

    return true;
}

//*************************************************
//* TMdPrm                                        *
//*************************************************
TMdPrm::TMdPrm( string name, TTypeParam *tp_prm ) :
    TParamContr(name,tp_prm), p_el("w_attr"), acqErr(dataRes())
{

}

TMdPrm::~TMdPrm( )	{ nodeDelAll(); }

void TMdPrm::postEnable( int flag )
{
    TParamContr::postEnable(flag);
    if(!vlElemPresent(&p_el)) vlElemAtt(&p_el);
}

TMdContr &TMdPrm::owner( ) const	{ return (TMdContr&)TParamContr::owner(); }

void TMdPrm::enable( )
{
    if(enableStat()) return;

    TParamContr::enable();

    owner().prmEn(this, true);

    parseOIDList(OIDList());

    //Init attributes call
    // Start SNMP-net session
    if(owner().enableStat() || !owner().prmEnErr) {
	void *ss =  snmp_sess_open(owner().getSess());
	if(ss) {
	    try { upVal(ss, true); }
	    catch(TError &err) {
		owner().prmEnErr = true;
		mess_err(nodePath().c_str(),"%s",err.mess.c_str());
	    }
	    snmp_sess_close(ss);
	}
    }
}

void TMdPrm::disable( )
{
    if(!enableStat())  return;

    owner().prmEn(this, false);

    TParamContr::disable();

    //Set EVAL to parameter attributes
    vector<string> ls;
    elem().fldList(ls);
    for(unsigned i_el = 0; i_el < ls.size(); i_el++)
	vlAt(ls[i_el]).at().setS(EVAL_STR,0,true);
}

void TMdPrm::upVal( void *ss, bool onlyInit )
{
    int		el_cnt = 0;
    string	soid;
    char	tbuf[100];
    struct snmp_pdu *response;
    struct variable_list *var;
    oid oid_root[MAX_OID_LEN], oid_next[MAX_OID_LEN];
    size_t oid_root_len = MAX_OID_LEN, oid_next_len = MAX_OID_LEN;

    vector<string> als;

    try {
    for(unsigned ioid = 0; ioid < lsOID().size(); ioid++) {
	oid_root_len = oid_next_len = lsOID()[ioid].size()/sizeof(oid);
	memmove(oid_root, lsOID()[ioid].data(), oid_root_len*sizeof(oid));
	memmove(oid_next, oid_root, oid_root_len*sizeof(oid));

	bool isScalar = oid_root_len && (oid_root[oid_root_len-1] == 0);
	bool running = true;
	while(running && (el_cnt++) < owner().pAttrLimit()) {
	    struct snmp_pdu *pdu = snmp_pdu_create(isScalar ? SNMP_MSG_GET : SNMP_MSG_GETNEXT);
	    snmp_add_null_var(pdu, oid_next, oid_next_len);
	    int status = snmp_sess_synch_response(ss, pdu, &response);
	    if(status == STAT_SUCCESS && response && response->errstat == SNMP_ERR_NOERROR)
		for(var = response->variables; var; var = var->next_variable) {
		    if((var->name_length < oid_root_len) || (memcmp(oid_root,var->name,oid_root_len*sizeof(oid)) != 0)) {
			running = false;
			continue;
		    }
		    // Get or create element
		    soid = owner().oid2str(var->name,var->name_length);
		    als.push_back(soid);
		    if(!elem().fldPresent(soid)) {
			TFld::Type tp = (TFld::Type)-1;
			unsigned flg = TVal::DirWrite;
			string selIds, selLabs;

			snprint_objid(tbuf,sizeof(tbuf),var->name,var->name_length);

			struct tree *subtree = get_tree_head();
			if(subtree) subtree = get_tree(var->name, var->name_length, subtree);
			if(subtree) {
			    if(!(subtree->access == MIB_ACCESS_READWRITE || subtree->access == MIB_ACCESS_WRITEONLY))
				flg |= TFld::NoWrite;
			    if(subtree->enums) {
				flg |= TFld::Selectable;
				for(struct enum_list *enums = subtree->enums; enums; enums = enums->next) {
				    selIds += i2s(enums->value)+";";
				    selLabs += string(enums->label)+";";
				}
			    }
			}

			switch(var->type) {
			    case ASN_BOOLEAN:		tp = TFld::Boolean;	break;
			    case ASN_INTEGER:		tp = TFld::Integer;	break;
			    case ASN_OPAQUE_FLOAT:
			    case ASN_OPAQUE_DOUBLE:	tp = TFld::Real;	break;
			    case ASN_GAUGE:
			    case ASN_COUNTER:
			    case ASN_TIMETICKS:
			    case ASN_UINTEGER:		tp = TFld::Real;	break;
			    case ASN_OCTET_STR:
			    case ASN_OPAQUE:
			    case ASN_IPADDRESS:
			    case ASN_OBJECT_ID:
			    case ASN_COUNTER64:
			    case ASN_OPAQUE_COUNTER64:
			    case ASN_OPAQUE_U64:
			    case ASN_OPAQUE_I64:
			    case ASN_BIT_STR:		tp = TFld::String;	break;
			    case SNMP_ENDOFMIBVIEW:
			    case SNMP_NOSUCHOBJECT:
			    case SNMP_NOSUCHINSTANCE:	running = false;	break;
			}
			if(tp >= 0)
			    elem().fldAdd(new TFld(soid.c_str(),tbuf,tp,flg,"","",
				selIds.c_str(),selLabs.c_str(),(i2s(var->type)+"\n"+(subtree->hint?subtree->hint:"")).c_str()));
		    }
		    // Set value
		    if(!onlyInit) {
			AutoHD<TVal> attr = vlAt(soid);

			switch(var->type)
			{
			    case ASN_BOOLEAN: attr.at().setB((bool)*var->val.integer, 0, true);	break;
			    case ASN_INTEGER: attr.at().setI(*var->val.integer, 0, true);	break;
			    case ASN_GAUGE:
			    case ASN_COUNTER:
			    case ASN_TIMETICKS:
			    case ASN_UINTEGER: attr.at().setR(*(unsigned long*)var->val.integer, 0, true);	break;
			    case ASN_OCTET_STR:
			    case ASN_OPAQUE: {
				string hint = TSYS::strLine(attr.at().fld().reserve(),1);
				size_t fndPos;
				if((fndPos=hint.find("x")) != string::npos)
				    hint = TSYS::strDecode(string((char*)var->val.string,var->val_len), TSYS::Bin,
					((fndPos+1)<hint.size())?hint.substr(fndPos+1,1):"");
				else hint = string((char*)var->val.string,var->val_len);
				attr.at().setS(hint, 0, true);
				break;
			    }
			    case ASN_IPADDRESS: {
				u_char *ip = (u_char*)var->val.string;
				attr.at().setS(TSYS::strMess("%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]), 0, true);
				break;
			    }
			    case ASN_OBJECT_ID:
				snprint_objid(tbuf, sizeof(tbuf), (oid*)(var->val.objid), var->val_len/sizeof(oid));
				attr.at().setS(tbuf, 0, true);
				break;
			    case ASN_COUNTER64:
			    case ASN_OPAQUE_COUNTER64:
			    case ASN_OPAQUE_U64:
				printU64(tbuf,(struct counter64*)var->val.counter64);
				attr.at().setS(tbuf,0,true);
				break;
			    case ASN_OPAQUE_I64:
				printI64(tbuf,(struct counter64 *)var->val.counter64);
				attr.at().setS(tbuf,0,true);
				break;
			    case ASN_BIT_STR:
				snprint_bitstring(tbuf, sizeof(tbuf), var, NULL, NULL, NULL);
				attr.at().setS(tbuf,0,true);
				break;
			    case ASN_OPAQUE_FLOAT:
				if(var->val.floatVal) attr.at().setR(*var->val.floatVal,0,true);
				break;
			    case ASN_OPAQUE_DOUBLE:
				if(var->val.doubleVal) attr.at().setR(*var->val.doubleVal,0,true);
				break;
			    default:
				mess_warning(owner().nodePath().c_str(), _("ASN type '%d' is not supported."), var->type);
				//print_objid(var->name,var->name_length);
				//print_value(var->name,var->name_length,var);
				break;
			}
		    }
		    if(isScalar) { running = false; break; }
		    if(running) {
			memmove((char*)oid_next, (char*)var->name, var->name_length*sizeof(oid));
			oid_next_len = var->name_length;
		    }
		}
	    else if(status == STAT_TIMEOUT)
		throw TError(owner().nodePath().c_str(),TSYS::strMess(_("10:Timeout: No response from %s."),owner().session.peername).c_str());
	    else running = 0;
	    if(response) snmp_free_pdu(response);
	}
    }
    acqErr = "";
    } catch(TError &err) { acqErr = err.mess; }

    //Check for delete DAQ parameter's attributes
    if(onlyInit)
	for(int iP = 0; iP < (int)elem().fldSize(); iP++) {
	    unsigned iL;
	    for(iL = 0; iL < als.size(); iL++)
		if(elem().fldAt(iP).name() == als[iL])
		    break;
	    if(iL >= als.size())
		try{ elem().fldDel(iP); iP--; }
		catch(TError &err){ mess_warning(err.cat.c_str(),err.mess.c_str()); }
	}
    //Set the attributes to EVAL for error connection
    else if(acqErr.getVal().size())
	for(int iP = 0; iP < (int)elem().fldSize(); iP++)
	    vlAt(elem().fldAt(iP).name()).at().setS(EVAL_STR, 0, true);

    if(acqErr.getVal().size())	throw TError(owner().nodePath(), acqErr.getVal());
}

void TMdPrm::parseOIDList( const string &ioid )
{
    cfg("OID_LS").setS(ioid);

    oid tmpoid[MAX_OID_LEN];
    size_t tmpoid_len = MAX_OID_LEN;

    ls_oid.clear();

    string sel;
    for(int ioff = 0; (sel=TSYS::strSepParse(OIDList(),0,'\n',&ioff)).size(); ) {
	if(sel[0] == '#') continue;
	tmpoid_len = MAX_OID_LEN;
	if(snmp_parse_oid(sel.c_str(),tmpoid,&tmpoid_len))
	    ls_oid.push_back(string((char*)tmpoid,tmpoid_len*sizeof(oid)));
    }
}

void TMdPrm::cntrCmdProc( XMLNode *opt )
{
    //Service commands process
    string a_path = opt->attr("path");
    if(a_path.find("/serv/") == 0)	{ TParamContr::cntrCmdProc(opt); return; }

    //Get page info
    if(opt->name() == "info") {
	TParamContr::cntrCmdProc(opt);
	ctrMkNode3("fld",opt,-1,"/prm/cfg/OID_LS",EVAL_STR,SEC_RD|(enableStat()?0:SEC_WR), "SnthHgl","1", "rows","8",
	    "help",_("The list of SNMP OIDs, including directories to get all of the subelements. The OID can be written as follows:\n"
		"  \".1.3.6.1.2.1.1\" - direct code addressing for the object \"System\";\n"
		"  \".iso.org.dod.internet.mgmt.mib-2.system\" - full symbolic addressing to direct one for the object \"System\";\n"
		"  \"system.sysDescr.0\" - simple, not full path, addressing from the root alias (the object \"System\");\n"
		"  \"SNMPv2-MIB::sysDescr.0\" - addressing from the MIB base by the module name for \"system.sysDescr.0\"."));
	if(get_tree_head())
	    ctrMkNode3("fld",opt,-1,"/prm/cfg/MIB",_("MIB Tree"),enableStat()?0:RWRW__, "dest","select", "select","/prm/cfg/MIB_lst");
	return;
    }

    //Process command to page
    if(a_path == "/prm/cfg/OID_LS" && ctrChkNode2(opt,"SnthHgl",SEC_RD))
	opt->childAdd("rule")->setAttr("expr","^#[^\n]*")->setAttr("color","gray")->setAttr("font_italic","1");
    else if(a_path == "/prm/cfg/MIB") {
	if(ctrChkNode3(opt,"get",RWRW__,SEC_RD)) opt->setText(TBDS::genPrmGet(nodePath()+"selOID","",opt->attr("user")));
	if(ctrChkNode3(opt,"set",RWRW__,SEC_WR)) {
	    if(opt->text() == _("<<Append current>>")) {
		oid oidn[MAX_OID_LEN];
		size_t oidn_len = MAX_OID_LEN;
		string baseIt = TBDS::genPrmGet(nodePath()+"selOID","",opt->attr("user"));
		if(snmp_parse_oid(baseIt.c_str(),oidn,&oidn_len)) {
		    string vLs = OIDList(), vS;
		    for(int off = 0; (vS=TSYS::strLine(vLs,0,&off)).size() && vS != baseIt; ) ;
		    if(vS.empty()) setOIDList(vLs+((vLs.size() && vLs[vLs.size()-1] != '\n')?"\n":"")+baseIt);
		}
	    } else TBDS::genPrmSet(nodePath()+"selOID", opt->text(), opt->attr("user"));
	}
    }
    else if(a_path == "/prm/cfg/MIB_lst" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	oid oidn[MAX_OID_LEN];
	size_t oidn_len = MAX_OID_LEN;
	string baseIt = TBDS::genPrmGet(nodePath()+"selOID","",opt->attr("user")), baseIt_, baseIt_s;
	struct tree *subTr = get_tree_head();
	if(snmp_parse_oid(baseIt.c_str(),oidn,&oidn_len)) subTr = get_tree(oidn, oidn_len, subTr);
	else baseIt = ".iso";

	opt->childAdd("el")->setText(_("<<Append current>>"));

	//Previous
	for(int off = 1; (baseIt_=TSYS::strParse(baseIt,0,".",&off)).size(); )
	    opt->childAdd("el")->setText((baseIt_s+="."+baseIt_));

	//Childs
	for(struct tree *chtree = subTr->child_list; chtree; chtree = chtree->next_peer)
	    opt->childAdd("el")->setText(baseIt+"."+chtree->label);
    }
    else TParamContr::cntrCmdProc(opt);
}

void TMdPrm::vlGet( TVal &vo )
{
    TParamContr::vlGet(vo);

    if(vo.name() == "err") {
	if(acqErr.getVal().size()) vo.setS(acqErr.getVal(), 0, true);
	else vo.setS("0",0,true);
    }
}

void TMdPrm::vlSet( TVal &vo, const TVariant &vl, const TVariant &pvl )
{
    if(!enableStat() || !owner().startStat()) { vo.setS(EVAL_STR, 0, true); return; }

    //Send to active reserve station
    if(vlSetRednt(vo,vl,pvl))	return;

    //Direct write
    void *ss;
    char vtp = 0;
    oid oidn[MAX_OID_LEN];
    size_t oidn_len = MAX_OID_LEN;
    struct snmp_pdu *response = NULL;

    struct snmp_pdu *pdu = snmp_pdu_create(SNMP_MSG_SET);
    owner().str2oid(vo.name(), oidn, oidn_len);

    TVariant wVl = vl;
    switch(s2i(vo.fld().reserve())) {
	case ASN_INTEGER:	vtp = 'i';	break;
	case ASN_GAUGE:		vtp = 'u';	break;
	case ASN_COUNTER:	vtp = 'c';	break;
	case ASN_TIMETICKS:	vtp = 't';	break;
	case ASN_UINTEGER:	vtp = '3';	break;
	case ASN_OCTET_STR:
	case ASN_OPAQUE:	vtp = 's';	break;
	case ASN_IPADDRESS:	vtp = 'a';	break;
	case ASN_OBJECT_ID:	vtp = 'o';	break;
	case ASN_COUNTER64:
	case ASN_OPAQUE_COUNTER64:
	case ASN_OPAQUE_U64:	vtp = 'C';	break;
	case ASN_OPAQUE_I64:	break;
	case ASN_BIT_STR:	vtp = 'b';	break;
	case ASN_OPAQUE_FLOAT:	break;
	case ASN_OPAQUE_DOUBLE:	break;
	default:		break;
    }
    if(vtp) {
	if(!(ss=snmp_sess_open(&owner().session))) return;

	snmp_add_var(pdu, oidn, oidn_len, vtp, vl.getS().c_str());
	int status = snmp_sess_synch_response(ss, pdu, &response);
	if(status == STAT_TIMEOUT)
	    owner().acqErr.setVal(TSYS::strMess(_("10:Timeout: No response from %s."),owner().session.peername).c_str());
	else if(response && response->errstat == SNMP_ERR_NOSUCHNAME)
	    owner().acqErr.setVal(TSYS::strMess(_("11:Unauthorized name.")));
	if(response) snmp_free_pdu(response);
	snmp_sess_close(ss);
    }
}

void TMdPrm::vlArchMake(TVal &val)
{
    TParamContr::vlArchMake(val);

    if(val.arch().freeStat()) return;
    val.arch().at().setSrcMode(TVArchive::SRC_DAQAttr);
    val.arch().at().setPeriod(owner().period() ? owner().period()/1000 : 1000000);
    val.arch().at().setHardGrid(true);
    val.arch().at().setHighResTm(true);
}
