//OpenSCADA MMS(IEC-9506) implementation library file: libMMS.cpp /****************************************************************************** * Copyright (C) 2014-2021 by Roman Savochenko, * * * * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation; version 3 of the License. * * * * This library 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 Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ******************************************************************************/ #include #include #include #include #include #include #include #include "openssl/bio.h" #include "openssl/ssl.h" #include "openssl/aes.h" #include "openssl/err.h" #include #include "openssl/hmac.h" #include "openssl/evp.h" #include "libMMS.h" namespace MMS { //************************************************* //* Static methods, mostly from OpenSCADA::TSYS * //************************************************* int64_t curTime( ) { timeval cur_tm; gettimeofday(&cur_tm, NULL); return (int64_t)cur_tm.tv_sec*1000000 + cur_tm.tv_usec; } string int2s( int val ) { char buf[250]; snprintf(buf, sizeof(buf), "%d", val); return buf; } string uint2s( unsigned val ) { char buf[250]; snprintf(buf, sizeof(buf), "%u", val); return buf; } string ll2s( int64_t val ) { char buf[250]; snprintf(buf, sizeof(buf), "%lld", (long long int)val); return buf; } string r2s( double val, int prec, char tp ) { char buf[250]; prec = std::max(0, prec); switch(tp) { case 'g': snprintf(buf, sizeof(buf), "%.*g", prec, val); break; case 'e': snprintf(buf, sizeof(buf), "%.*e", prec, val); break; default: snprintf(buf, sizeof(buf), "%.*f", prec, val); break; } return buf; } double s2r( const string &val ) { return atof(val.c_str()); } /*{ const char *chChr = val.c_str(); //Pass spaces before for( ; true; ++chChr) { switch(*chChr) { case ' ': case '\t': continue; } break; } //Check and process the base bool isNeg = false, isExpNeg = false; double tVl = 0; int16_t nAftRdx = 0, tAftRdx = 0; if(*chChr && ((*chChr >= '0' && *chChr <= '9') || *chChr == '-' || *chChr == '+')) { if(*chChr == '+') ++chChr; else if(*chChr == '-') { isNeg = true; ++chChr; } for(bool notFirst = false; *chChr >= '0' && *chChr <= '9'; ++chChr, notFirst = true) { if(notFirst) tVl *= 10; tVl += *chChr - '0'; } } if(*chChr == '.' || *chChr == ',') { for(++chChr; *chChr >= '0' && *chChr <= '9'; ++chChr, ++nAftRdx) tVl = tVl*10 + (*chChr - '0'); } if(isNeg) tVl *= -1; //Read exponent if(*chChr && (*chChr == 'e' || *chChr == 'E')) { ++chChr; if(*chChr == '+') ++chChr; else if(*chChr == '-') { isExpNeg = true; ++chChr; } for(bool notFirst = false; *chChr >= '0' && *chChr <= '9'; ++chChr, notFirst = true) { if(notFirst) tAftRdx *= 10; tAftRdx += *chChr - '0'; } if(isExpNeg) tAftRdx *= -1; } //Combine return tVl * pow(10, tAftRdx-nAftRdx); }*/ string strParse( const string &path, int level, const string &sep, int *off, bool mergeSepSymb ) { int an_dir = off ? *off : 0; int t_lev = 0; size_t t_dir; if(an_dir >= (int)path.size() || sep.empty()) return ""; while(true) { t_dir = path.find(sep, an_dir); if(t_dir == string::npos) { if(off) *off = path.size(); return (t_lev == level) ? path.substr(an_dir) : ""; } else if(t_lev == level) { if(off) *off = t_dir+sep.size(); return path.substr(an_dir, t_dir-an_dir); } if(mergeSepSymb && sep.size() == 1) for(an_dir = t_dir; an_dir < (int)path.size() && path[an_dir] == sep[0]; ) an_dir++; else an_dir = t_dir+sep.size(); t_lev++; } return ""; } string strMess( const char *fmt, ... ) { char str[10000]; va_list argptr; va_start(argptr, fmt); vsnprintf(str, sizeof(str), fmt, argptr); va_end(argptr); return str; } void setBS( string &vl, uint32_t n ) { if(n/8 >= vl.size()) vl.append((n/8+1)-vl.size(), char(0)); vl[n/8] |= 0x80>>(n%8); } bool tryBS( const string &vl, uint32_t n ) { if(n/8 >= vl.size()) return false; return vl[n/8]&(0x80>>(n%8)); } uint16_t i16_LE( uint16_t in ) { #if __BYTE_ORDER == __BIG_ENDIAN return bswap_16(in); #endif return in; } uint32_t i32_LE( uint32_t in ) { #if __BYTE_ORDER == __BIG_ENDIAN return bswap_32(in); #endif return in; } uint64_t i64_LE( uint64_t in ) { #if __BYTE_ORDER == __BIG_ENDIAN return bswap_64(in); #endif return in; } //************************************************* //* MMS object * //************************************************* Core::Core( ) { } Core::~Core( ) { } const char *Core::iVal( const string &rb, int &off, char vSz ) { off += vSz; if(off > (int)rb.size()) throw Error("Buffer size is lesser requested value."); return rb.data()+off-vSz; } uint32_t Core::iN( const string &rb, int &off, uint8_t vSz ) { vSz = std::max(0, std::min((int)vSz,4)); if((off+vSz) > (int)rb.size()) throw Error("Buffer size is lesser requested value."); union { uint32_t v; char c[4]; } dt; dt.v = 0; while(vSz) dt.c[--vSz] = rb[off++]; return i32_LE(dt.v); } int Core::ASN_i( const string &rb, int &off, int offLim ) { if((off+2) > (int)rb.size()) throw Error("Buffer size is lesser to requested tag."); if((uint8_t)rb[off] == 0x9f || (uint8_t)rb[off] == 0xbf) off++; //Pass big tag off++; //Pass tag if(off > (int)rb.size()) throw Error("Buffer size is lesser to requested tag."); int sz = (uint8_t)rb[off++]; if(sz&0x80) { union { uint32_t v; char c[4]; } dt; dt.v = 0; int szB = sz & (~0x80); if(szB > 4) throw Error("Size too big for numbers."); if((off+szB) > (int)rb.size()) throw Error("Buffer size is lesser to requested value."); while(szB) dt.c[--szB] = rb[off++]; sz = i32_LE(dt.v); } if((off+sz) > (int)rb.size()) throw Error("Buffer size is lesser to requested value."); if(offLim >= 0 && off > offLim) throw Error("Variable excel container size."); return sz; } uint16_t Core::ASN_iTAG( const string &rb, int off ) { uint16_t rez = 0; if((off+2) > (int)rb.size()) throw Error("Buffer size is lesser to requested tag."); if(uint8_t(rb[off]) == 0x9f || uint8_t(rb[off]) == 0xbf) rez = uint8_t(rb[off++])<<8; rez |= uint8_t(rb[off++]); return i16_LE(rez); } uint32_t Core::ASN_iN( const string &rb, int &off, int sz ) { if(sz < 0) sz = ASN_i(rb, off); if(sz > 4) throw Error("Size too big for integer."); union { uint32_t v; char c[4]; } dt; dt.v = 0; while(sz) dt.c[--sz] = rb[off++]; return i32_LE(dt.v); } double Core::ASN_iR( const string &rb, int &off, int sz ) { if(sz < 0) sz = ASN_i(rb, off); if(sz < 5) throw Error("Size too little for real."); char exp = rb[off++]; sz--; if(exp == 8 && sz == 4) { union { float v; char c[4]; uint32_t vi; } dt; dt.v = 0; while(sz) dt.c[--sz] = rb[off++]; dt.vi = i32_LE(dt.vi); return dt.v; } else if(exp == 11 && sz == 8) { union { double v; char c[8]; uint64_t vi; } dt; dt.v = 0; while(sz) dt.c[--sz] = rb[off++]; dt.vi = i64_LE(dt.vi); return dt.v; } throw Error("Discrepancy exponent to size."); } string Core::ASN_iS( const string &rb, int &off, int sz ) { if(sz < 0) sz = ASN_i(rb, off); off += sz; return rb.substr(off-sz,sz); } string Core::ASN_iBS( const string &rb, int &off, int sz, char *unUsBits ) { if(sz < 0) sz = ASN_i(rb, off); if(unUsBits) *unUsBits = rb[off]; off += sz; return rb.substr(off-(sz-1),sz-1); } void Core::ASN_iAccessResult( const string &buf, int &off, int sz, XML_N &io ) { const char *errS; for(int offC = off, i_it = 0, err, vTp; (offC+sz) != off; i_it++) { int offC1 = off, szC1 = ASN_i(buf, off, offC+sz); while(i_it >= (int)io.childSize()) io.childAdd("itC"); XML_N *chN = io.childGet(i_it); switch((vTp=ASN_iTAG(buf,offC1)&~(0x20))) { case VT_Error: switch((err=ASN_iN(buf,off,szC1))) { case 0: errS = "object-invalidated"; break; case 1: errS = "hardware-fault"; break; case 2: errS = "temporarily-unavailable"; break; case 3: errS = "object-access-denied"; break; case 4: errS = "object-undefined"; break; case 5: errS = "invalid-address"; break; case 6: errS = "type-unsupported"; break; case 7: errS = "type-inconsistent"; break; case 8: errS = "object-attribute-inconsistent"; break; case 9: errS = "object-access-unsupported"; break; case 10: errS = "object-non-existent"; break; default: errS = "unknown"; break; } chN->setAttr("err", int2s(err)+":"+errS); break; case VT_Array: case VT_Struct: chN->setAttr("tp",int2s(vTp)); ASN_iAccessResult(buf, off, szC1, *chN); break; case VT_Bool: chN->setAttr("tp",int2s(vTp))->setText(int2s(ASN_iN(buf,off,szC1))); break; case VT_BitString: { //?!?! Need for test char unUsBits = 0; string vl, value = ASN_iBS(buf, off, szC1, &unUsBits); for(int i_vl = 0; i_vl < std::max(0,int(value.size()*8-std::max(0,int(unUsBits)))); i_vl++) vl += ((value[i_vl/8]>>(i_vl%8))&1) ? "1" : "0"; chN->setAttr("tp",int2s(vTp))->setText(vl); break; } case VT_OctString: { //?!?! Need for test string vl, value = ASN_iS(buf, off, szC1); for(unsigned i_vl = 0; i_vl < value.size(); i_vl++) vl += strMess("%0.2x",value[i_vl]); chN->setAttr("tp",int2s(vTp))->setText(vl); break; } case VT_VisString: chN->setAttr("tp",int2s(vTp))->setText(ASN_iS(buf,off,szC1)); break; case VT_Int: chN->setAttr("tp",int2s(vTp))->setText(int2s((int)ASN_iN(buf,off,szC1))); break; case VT_UInt: chN->setAttr("tp",int2s(vTp))->setText(uint2s(ASN_iN(buf,off,szC1))); break; case VT_Float: chN->setAttr("tp",int2s(vTp))->setText(r2s(ASN_iR(buf,off,szC1))); break; default: off += szC1; } } } void Core::oN( string &buf, uint32_t val, uint8_t sz, int off ) { union { uint32_t v; char c[4]; } dt; dt.v = i32_LE(val); if(sz < 0 || sz > 4) for(sz = 4; sz > 1 && !dt.c[sz-1]; ) sz--; off = (off >= 0) ? std::min(off,(int)buf.size()) : buf.size(); if((off+sz) > (int)buf.size()) buf.append(off+sz-buf.size(), char(0)); while(sz) buf[off++] = dt.c[--sz]; } void Core::ASN_o( string &buf, uint16_t tag, uint32_t sz ) { //Size extend int extB = 4; if(sz < 128) extB = 0; else for(sz = i16_LE(sz); sz >> (extB*8); ) extB++; //Tag bool bigTag = (tag >= 256); tag = i16_LE(tag); if(bigTag) buf += char((tag>>8)&0xFF); buf += char(tag&0xFF); //Size if(!extB) buf += char(sz); else { buf += char(0x80|extB); for(int i_ex = 0; i_ex < extB; i_ex++) buf += char((sz>>(extB-i_ex)*8)&0xFF); } } int Core::ASN_oC( string &buf, uint16_t tag, int off ) { //Position int elOff = off = (off >= 0) ? std::min(off,(int)buf.size()) : buf.size(); int sz = buf.size() - off; //Size extend int extB = 4; if(sz < 128) extB = 0; else { union { uint32_t v; char c[4]; } dt; dt.v = i32_LE(sz); while(!dt.c[extB-1]) extB--; } //Tag bool bigTag = (tag >= 256); buf.insert(elOff, ((bigTag?3:2)+extB), (char)0); tag = i16_LE(tag); if(bigTag) buf[off++] = char((tag>>8)&0xFF); buf[off++] = char(tag&0xFF); //Size if(!extB) buf[off++] = sz; else { buf[off++] = 0x80|extB; union { uint32_t v; char c[4]; } dt; dt.v = i32_LE(sz); while(extB) buf[off++] = dt.c[--extB]; } return elOff; } void Core::ASN_oN( string &buf, uint8_t tag, uint32_t val, uint8_t szMin ) { union { uint32_t v; char c[4]; } dt; dt.v = i32_LE(val); int sz = std::min(4, (int)szMin); while(val >> (sz*8)) sz++; ASN_o(buf, tag, sz); while(sz) buf += dt.c[--sz]; } void Core::ASN_oR( string &buf, uint8_t tag, double val, char exp ) { int sz = 0; if(exp == 8) { sz = 4; ASN_o(buf, tag, sz+1); buf += exp; union { float v; char c[4]; uint32_t vi; } dt; dt.v = val; dt.vi = i32_LE(dt.vi); while(sz) buf += dt.c[--sz]; } else if(exp == 11) { //!!!! sz = 8; ASN_o(buf, tag, sz+1); buf += exp; union { double v; char c[8]; uint64_t vi; } dt; dt.v = val; dt.vi = i64_LE(dt.vi); while(sz) buf += dt.c[--sz]; } else throw Error("Unsupported exponent size."); } void Core::ASN_oS( string &buf, uint8_t tag, const string &vl ) { ASN_o(buf, tag, vl.size()); buf += vl; } void Core::ASN_oBS( string &buf, uint8_t tag, const string &vl, char unUsBits ) { ASN_o(buf, tag, vl.size()+1); buf += char(unUsBits); buf += vl; } void Core::ASN_oNmObj( string &buf, uint8_t tag, const string &vl, const string &domain ) { int off = buf.size(); if(!domain.size()) ASN_oS(buf, 0x80, vl); // vmd-specific else { // domain-specific ASN_oS(buf, 0x1A, domain); // domainId ASN_oS(buf, 0x1A, vl); // itemId ASN_oC(buf, 0xA1, off); // domain-specific } //ASN_oS(rez, 0x82, vl); // aa-specific ASN_oC(buf, tag, off); //ObjectName } void Core::ASN_iTypeSpec( const string &buf, int &off, int sz, XML_N &io ) { for(int offC = off, i_it = 0, vTp; (offC+sz) != off; i_it++) { int offC1 = off, szC1 = ASN_i(buf, off, offC+sz); switch((vTp=ASN_iTAG(buf,offC1))) { case 0x80: io.setAttr("typeName", ASN_iS(buf,off,szC1)); break; case VT_Array: off += szC1; io.setAttr("tp", int2s(vTp)); for(offC1 = off; (offC1+szC1) != off; ) { int offC2 = off, szC2 = ASN_i(buf, off, offC1+szC1); switch(ASN_iTAG(buf,offC2)) { case 0x80: io.setAttr("packed",int2s(ASN_iN(buf,off,szC2))); break; // packed case 0xA1: for(offC2 = off; (offC2+szC2) != off; ) { int offC3 = off, szC3 = ASN_i(buf, off, offC2+szC2); if(ASN_iTAG(buf,offC3) == 0xA1) for(offC3 = off; (offC3+szC3) != off; ) { int offC4 = off, szC4 = ASN_i(buf, off, offC3+szC3); if(ASN_iTAG(buf,offC4) == 0x81) io.setAttr("numberOfElements",int2s(ASN_iN(buf,off,szC4))); else off += szC4; } else off += szC3; } break; case 0xA2: ASN_iTypeSpec(buf, off, szC2, io); break; // elementType default: off += szC2; break; } } break; case VT_Struct: io.setAttr("tp",int2s(vTp)); for(offC1 = off; (offC1+szC1) != off; ) { int offC2 = off, szC2 = ASN_i(buf, off, offC1+szC1); switch(ASN_iTAG(buf,offC2)) { case 0x80: io.setAttr("packed",int2s(ASN_iN(buf,off,szC2))); break; // packed case 0xA1: // components for(offC2 = off; (offC2+szC2) != off; ) { int offC3 = off, i_it = 0, szC3 = ASN_i(buf, off, offC2+szC2); if(ASN_iTAG(buf,offC3) == 0x30) // item for(offC3 = off; (offC3+szC3) != off; i_it++) { while(i_it < (int)io.childSize()) io.childAdd("stIt"); XML_N *chN = io.childGet(i_it); int szC4 = ASN_i(buf, off, offC3+szC3); switch(ASN_iTAG(buf,offC3)) { case 0x80: chN->setAttr("id",ASN_iS(buf,off,szC4)); break; //componentName case 0xA1: ASN_iTypeSpec(buf, off, szC4, *chN); break; //componentType default: off += szC4; break; } } else off += szC3; } break; default: off += szC2; break; } } break; case VT_UInt: io.setAttr("tp", int2s(vTp))->setAttr("sz", uint2s(ASN_iN(buf,off,szC1))); break; case VT_Int: case VT_VisString: case VT_OctString: case VT_BitString: io.setAttr("tp", int2s(vTp))->setAttr("sz", int2s((int)ASN_iN(buf,off,szC1))); break; case VT_Bool: case VT_Float: io.setAttr("tp",int2s(vTp)); off += szC1; break; default: io.setAttr("tp",int2s(VT_VisString)); off += szC1; break; } } } //************************************************* //* Protocol MMS client part * //************************************************* Client::Client( ) : isInitiated(false), mInvokeID(1), m_callParameterCBB("\xFB\00",2), m_callServicesSupported("\xEE\x00\x18\x3F\x0F\xF6\x10\x03\x01\xF8\x90",11) { //Reject errors const char *nms1[] = { "RejectConfirmedReq", "other", "unrecognized-service", "unrecognized-modifier", "invalid-invokeID", "invalid-argument", "invalid-modifier", "max-serv-outstanding-exceeded", "", "max-recursion-exceeded", "value-out-of-range" }; RejectErrs[0x80] = vector(nms1, nms1+11); const char *nms2[] = { "RejectConfirmedResp", "other", "unrecognized-service", "invalid-invokeID", "invalid-result", "", "max-recursion-exceeded", "value-out-of-range" }; RejectErrs[0x81] = vector(nms2, nms2+8); const char *nms3[] = { "RejectConfirmedError", "other", "unrecognized-service", "invalid-invokeID", "invalid-serviceError", "value-out-of-range" }; RejectErrs[0x82] = vector(nms3, nms3+6); const char *nms4[] = { "RejectUnconfirmed", "other", "unrecognized-service", "invalid-argument", "max-recursion-exceeded", "value-out-of-range" }; RejectErrs[0x83] = vector(nms4, nms4+6); //RejectPduError, RejectCancelRequest, RejectCancelResponse, RejectCancelError, RejectConcludeRequest,RejectConcludeResponse, RejectConcludeError; //Confirm errors const char *nmsC1[] = { "vmd-state", "other", "vmd-state-conflict", "vmd-operational-problem", "domain-transfer-problem", "state-machine-id-invalid" }; ConfirmErrs[0x80] = vector(nmsC1, nmsC1+6); const char *nmsC2[] = { "application-reference", "other", "aplication-unreachable", "connection-lost", "application-reference-invalid", "context-unsupported" }; ConfirmErrs[0x81] = vector(nmsC2, nmsC2+6); const char *nmsC3[] = { "definition", "other", "object-undefined", "invalid-address", "type-unsupported", "type-inconsistent", "object-exists", "object-attribute-inconsistent" }; ConfirmErrs[0x82] = vector(nmsC3, nmsC3+8); const char *nmsC4[] = { "resource", "other", "memory-unavailable", "processor-resource-unavailable", "mass-storage-unavailable", "capability-unavailable", "capability-unknown" }; ConfirmErrs[0x83] = vector(nmsC4, nmsC4+7); const char *nmsC5[] = { "service", "other", "primitives-out-of-sequence", "object-sate-conflict", "pdu-size", "continuation-invalid", "object-constraint-conflict" }; ConfirmErrs[0x84] = vector(nmsC5, nmsC5+7); const char *nmsC6[] = { "service-preempt", "other", "timeout", "deadlock", "cancel" }; ConfirmErrs[0x85] = vector(nmsC6, nmsC6+5); const char *nmsC7[] = { "time-resolution", "other", "unsupportable-time-resolution" }; ConfirmErrs[0x86] = vector(nmsC7, nmsC7+3); } Client::~Client( ) { } void Client::setCallParameterCBB( const string &val ) { m_callParameterCBB = val; m_callParameterCBB.resize(2, 0); } void Client::setCallServicesSupported( const string &val ) { m_callServicesSupported = val; m_callServicesSupported.resize(11, 0); } void Client::reset( ) { isInitiated = false; mInvokeID = 1; id_vName = id_modelName = id_rev = ""; } void Client::protIO( XML_N &io ) { string tpkt, rez, rez1, err; int sCd = 0; char buf[1000]; try { //bool debug = (bool)atoi(io.attr("debug").c_str()); if(io.name() != "MMS") throw Error("Unknown target protocol '%s'.", io.name().c_str()); //>TPKT (RFC-1006) oN(tpkt, MMS_TPKT_Version, 1); // Version oN(tpkt, 0, 1); // Reserved int tpktLenOff = tpkt.size(); oN(tpkt, 0, 2); // Length //Service's requests prepare if(io.attr("id") == "connect") { //>COTP ConnRequest (ISO-8073) oN(rez, 0, 1); // Length oN(rez, COTP_CR, 1); // PDU Type oN(rez, 0, 2); // Dest reference oN(rez, 0, 2); // Source reference oN(rez, 0, 1); // Class // Options if(COTP_SzTPDU() > 0) { // TPDU size oN(rez, COTP_O_SzTPDU, 1); // Code oN(rez, 1, 1); // Length oN(rez, COTP_SzTPDU(), 1); // Value } // Src TSAP oN(rez, COTP_O_SrcTSAP, 1); // Code oN(rez, 2, 1); // Length oN(rez, 0, 2); // Value // Dest TSAP oN(rez, COTP_O_DstTSAP, 1); // Code oN(rez, 2, 1); // Length oN(rez, COTP_DestTSAP(), 2); // Value oN(rez, rez.size()-1, 1, 0); //>COTP real length tpkt += rez; //if(debug) debugMess(strMess("ConnRequest Req(%d)",rez.size()); } else { if(io.attr("id") == "initiate") sCd = MMS_InitiateReq; else if(io.attr("id") == "identify") sCd = Conf_Identify; else if(io.attr("id") == "getNameList") sCd = Conf_getNameList; else if(io.attr("id") == "read") sCd = Conf_Read; else if(io.attr("id") == "write") sCd = Conf_Write; else if(io.attr("id") == "getVariableAccessAttributes") sCd = Conf_getVariableAccessAttributes; else if(io.attr("id") == "getCapabilityList") sCd = Conf_getCapabilityList; if(sCd) { //>COTP ConnRequest (ISO-8073) oN(rez, 0, 1); // Length oN(rez, COTP_DT, 1); // PDU Type oN(rez, COTP_LastDtUnit, 1); // Class oN(rez, rez.size()-1, 1, 0); //>COTP real length tpkt += rez; } rez = ""; //>MMS switch(sCd) { case MMS_InitiateReq: { //InitiateReq ASN_oN(rez, 0x80, 8187); // localDetailCalling ASN_oN(rez, 0x81, 3); // proposedMaxServOutstandingCalling ASN_oN(rez, 0x82, 3); // proposedMaxServOutstandingCalled ASN_oN(rez, 0x83, 127); // proposedDataStructureNestingLevel int c1off = rez.size(); // mmsInitRequestDetail ASN_oN(rez, 0x80, 1); // proposedVersionNumber ASN_oBS(rez, 0x81, callParameterCBB(), 5); // proposedParameterCBB ASN_oBS(rez, 0x82, callServicesSupported(), 3); // servicesSupportedCalling ASN_oC(rez, 0xA4, c1off); // mmsInitRequestDetail ASN_oC(rez, MMS_InitiateReq, 0); //InitiateReq break; } //ConfirmedRequest case Conf_Identify: case Conf_getCapabilityList: case Conf_getNameList: case Conf_Read: case Conf_Write: case Conf_getVariableAccessAttributes: ASN_oN(rez, 0x02, mInvokeID++); // invokeID switch(sCd) { case Conf_Identify: ASN_o(rez, Conf_Identify); break; // Identify case Conf_getNameList: { // getNameList int offTmp = rez.size(); // extendedObjectClass ASN_oN(rez, 0x80, atoi(io.attr("objectClass").c_str())); // objectClass ASN_oC(rez, 0xA0, offTmp); // extendedObjectClass int offTmp1 = rez.size(); // objectScope if(io.attr("domainSpecific").size()) ASN_oS(rez, 0x81, io.attr("domainSpecific")); //domainSpecific else ASN_oN(rez, 0x80, 0, 0); // NULL ASN_oC(rez, 0xA1, offTmp1); // objectScope if(io.attr("continueAfter").size()) ASN_oS(rez, 0x82, io.attr("continueAfter")); // continueAfter ASN_oC(rez, Conf_getNameList, offTmp); // getNameList break; } case Conf_Read: { int offTmp = rez.size(); // ReadService if(atoi(io.attr("withResult").c_str())) ASN_oN(rez, 0x80, 1); // specificationWithResult // variableSpecification int offTmp1 = rez.size(); // listOfVariable for(unsigned i_it = 0; i_it < io.childSize(); i_it++) { XML_N *itN = io.childGet(i_it)->setAttr("err",""); int offTmp2 = rez.size(); // VariableSpecification ASN_oNmObj(rez, 0xA0, itN->attr("itemId"), itN->attr("domainId"));// name //ASN_oAddr(rez, 0xA1, offTmp); // address // variableDescription // scatteredAccessDescription // invalidated ASN_oC(rez, 0x30, offTmp2); // VariableSpecification } ASN_oC(rez, 0xA0, offTmp1); // listOfVariable //ASN_oNmO(rez, 0xA1); // variableListName (ObjectName) ASN_oC(rez, 0xA1, offTmp1); // variableSpecification ASN_oC(rez, sCd, offTmp); // ReadWriteService break; } case Conf_Write: { int offTmp = rez.size(); // WriteService // listOfVariable for(unsigned i_it = 0; i_it < io.childSize(); i_it++) { XML_N *itN = io.childGet(i_it)->setAttr("err",""); int offTmp1 = rez.size(); // VariableSpecification ASN_oNmObj(rez, 0xA0, itN->attr("itemId"), itN->attr("domainId"));// name //ASN_oAddr(rez, 0xA1, offTmp); // address // variableDescription // scatteredAccessDescription // invalidated ASN_oC(rez, 0x30, offTmp1); // VariableSpecification } ASN_oC(rez, 0xA0, offTmp); // listOfVariable int offTmp1 = rez.size(); // listOfData for(unsigned i_it = 0, dTp = 0; i_it < io.childSize(); i_it++) { XML_N *itN = io.childGet(i_it); switch((dTp=atoi(itN->attr("dataType").c_str()))) { case VT_Bool: ASN_oN(rez, dTp, (bool)atoi(itN->text().c_str())); break; case VT_Int: ASN_oN(rez, dTp, atoi(itN->text().c_str())); break; case VT_UInt: ASN_oN(rez, dTp, (unsigned)atoi(itN->text().c_str()));break; case VT_Float: ASN_oR(rez, dTp, s2r(itN->text())); break; case VT_BitString: { //?!?! Need for test string srcS = itN->text(), rezBS; for(unsigned i_s = 0; i_s < srcS.size(); i_s++) if(srcS[i_s] == '1') setBS(rezBS, i_s); else if(srcS[i_s] != '0') break; ASN_oBS(rez, dTp, rezBS, atoi(itN->attr("unUsBits").c_str())); break; } case VT_OctString: { //?!?! Need for test string srcS = itN->text(), rezBS; for(unsigned i_s = 0; i_s < srcS.size(); i_s += 2) rezBS += (char)strtol(srcS.substr(i_s,2).c_str(),NULL,16); ASN_oS(rez, dTp, rezBS); break; } case VT_VisString: ASN_oS(rez, dTp, itN->text()); break; case VT_Array: case VT_Struct: { //?!?! Test implement for(unsigned i_c = 0, dTp1; i_c < itN->childSize(); i_c++) { XML_N *icN = itN->childGet(i_c); switch((dTp1=atoi(icN->attr("dataType").c_str()))) { case VT_Bool: ASN_oN(rez, dTp1, (bool)atoi(icN->text().c_str())); break; case VT_Int: ASN_oN(rez, dTp1, atoi(icN->text().c_str())); break; case VT_UInt: ASN_oN(rez, dTp1, (unsigned)atoi(icN->text().c_str())); break; case VT_Float: ASN_oR(rez, dTp1, s2r(icN->text())); break; } } ASN_oC(rez, dTp, offTmp1); break; } default: ASN_oS(rez, VT_VisString, itN->text()); break; } } ASN_oC(rez, 0xA0, offTmp1); // listOfData ASN_oC(rez, sCd, offTmp); // WriteService break; } case Conf_getVariableAccessAttributes: { int offTmp = rez.size(); ASN_oNmObj(rez, 0xA0, io.attr("itemId"), io.attr("domainId")); // name //ASN_oAddr(rez, 0xA1, offTmp); // address // ASN_oN(rez, 0xA0, ); // numericAddress // ASN_oS(rez, 0xA1, ); // symbolicAddress // ASN_oS(rez, 0xA2, ); // unconstrainedAddress ASN_oC(rez, sCd, offTmp); // ItemsService break; } case Conf_getCapabilityList: ASN_o(rez, Conf_getCapabilityList); break; // getCapabilityList } ASN_oC(rez, MMS_ConfirmedReq, 0); //ConfirmedRequest break; } tpkt += rez; } if(!rez.size()) throw Error("Unknown service '%s'.", io.attr("id").c_str()); oN(tpkt, tpkt.size(), 2, tpktLenOff); //>TPTK real length //Send request and wait respond int resp_len = messIO(tpkt.data(), tpkt.size(), buf, sizeof(buf)); tpkt.assign(buf, resp_len); int off = 2; for( ; tpkt.size() < 4 || tpkt.size() < iN(tpkt,off,2); off = 2) { if(!(resp_len=messIO(NULL,0,buf,sizeof(buf)))) throw Error("Not full respond."); tpkt.append(buf, resp_len); } off = 2; if(tpkt.size() < 4 || iN(tpkt,off,2) != tpkt.size()) // >::iterator iErr; if((errTp=ASN_iTAG(tpkt,offC1)) == 0x80) chErr += strMess("Invoke: %d. ",ASN_iN(tpkt,off,szC1)); else if((iErr=RejectErrs.find(errTp)) != RejectErrs.end()) chErr += (((errCode=ASN_iN(tpkt,off,szC1))+1 >= (int)iErr->second.size()) ? "unknown" : iErr->second[errCode+1]) + "("+iErr->second[0]+"). "; else off += szC1; } break; case MMS_ConfirmedErr: for(offC = off; (offC+szC) != off; ) { int offC1 = off, szC1 = ASN_i(tpkt, off, offC+szC); switch(ASN_iTAG(tpkt,offC1)) { case 0x80: chErr += strMess("Invoke: %d. ",ASN_iN(tpkt,off,szC1)); break; case 0xA2: { //serviceError chErr += "Service error: "; for(offC1 = off; (offC1+szC1) != off; ) { int offC2 = off, szC2 = ASN_i(tpkt, off, offC1+szC1); if(ASN_iTAG(tpkt,offC2) == 0xA0) //errorClass for(offC2 = off; (offC2+szC2) != off; ) { int offC3 = off, szC3 = ASN_i(tpkt, off, offC2+szC2); map >::iterator iErr; int errTp = ASN_iTAG(tpkt, offC3), errCode; if((iErr=ConfirmErrs.find(errTp)) != ConfirmErrs.end()) chErr += (((errCode=ASN_iN(tpkt,off,szC3))+1 >= (int)iErr->second.size()) ? "unknown" : iErr->second[errCode+1]) + "("+iErr->second[0]+"). "; else { chErr += "unknown"; off += szC3; } } else off += szC2; break; } break; } default: off += szC1; break; } } break; default: chErr += "Unknown"; off += szC; break; } throw Error("ConfirmedRequest error: %s", chErr.c_str()); } for(offC = off; (offC+szC) != off; ) { int offC1 = off, szC1 = ASN_i(tpkt, off, offC+szC); switch(ASN_iTAG(tpkt,offC1)) { case 0x02: { // invokeID uint32_t invokeID = ASN_iN(tpkt, off, szC1); if(invokeID != (mInvokeID-1)) { //The counter reset check by server if(invokeID < mInvokeID && (mInvokeID-invokeID) > 32768) mInvokeID = invokeID+1; else throw Error("Inconsistent invokeID: send=%u, get=%u.", mInvokeID-1, invokeID); } break; } case Conf_IdentifyResp: // Identify for(offC1 = off; (offC1+szC1) != off; ) { int offC2 = off, szC2 = ASN_i(tpkt, off, offC1+szC1); switch(ASN_iTAG(tpkt,offC2)) { case 0x80: io.setAttr("vendorName", ASN_iS(tpkt,off,szC2)); break; // vendorName case 0x81: io.setAttr("modelName", ASN_iS(tpkt,off,szC2)); break; // modelName case 0x82: io.setAttr("revision", ASN_iS(tpkt,off,szC2)); break; // revision default: off += szC2; break; } } break; case Conf_getNameList: // getNameList case Conf_getCapabilityList: { // getCapabilityList bool moreFollows = true; for(offC1 = off; (offC1+szC1) != off; ) { int offC2 = off, szC2 = ASN_i(tpkt, off, offC1+szC1); switch(ASN_iTAG(tpkt,offC2)) { case 0xA0: // listOfCapabilities for(offC2 = off; (offC2+szC2) != off; ) { int offC3 = off, szC3 = ASN_i(tpkt, off, offC2+szC2); if(ASN_iTAG(tpkt,offC3) == 0x1a) io.childAdd("it")->setText(ASN_iS(tpkt,off,szC3)); else off += szC3; } break; case 0x81: moreFollows = (bool)ASN_iN(tpkt,off,szC2); break; // moreFollows default: off += szC2; break; } } if(io.childSize()) io.setAttr("moreFollows", int2s(moreFollows)); break; } case Conf_Read: case Conf_Write: for(offC1 = off; (offC1+szC1) != off; ) { int offC2 = off, szC2 = ASN_i(tpkt, off, offC1+szC1); switch(ASN_iTAG(tpkt,offC2)) { case 0xA1: ASN_iAccessResult(tpkt, off, szC2, io); break; // listOfAccessResult default: off += szC2; break; } } break; case Conf_getVariableAccessAttributes: for(offC1 = off; (offC1+szC1) != off; ) { int offC2 = off, szC2 = ASN_i(tpkt, off, offC1+szC1); switch(ASN_iTAG(tpkt,offC2)) { case 0x80: io.setAttr("mmsDeletable", int2s((bool)ASN_iN(tpkt,off,szC2))); break; // mmsDeletable case 0xA2: ASN_iTypeSpec(tpkt, off, szC2, io); break; // typeSpecification default: off += szC2; break; } } break; default: off += szC1; break; } } break; } } } } catch(Error &er) { err = strMess("100:%s", er.mess.c_str()); } io.setAttr("err", err); } void Client::reqService( XML_N &io ) { io.setAttr("err", ""); if(!isInitiated) { XML_N req("MMS"); req.setAttr("id","connect"); protIO(req); if(!req.attr("err").empty()) { io.setAttr("err", req.attr("err")); return; } req.clear()->setAttr("id","initiate"); protIO(req); if(!req.attr("err").empty()) { io.setAttr("err", req.attr("err")); return; } isInitiated = true; } if(id_vName.empty()) { XML_N req("MMS"); req.setAttr("id","identify"); protIO(req); if(!req.attr("err").empty()) { io.setAttr("err", req.attr("err")); return; } id_vName = req.attr("vendorName"); id_modelName = req.attr("modelName"); id_rev = req.attr("revision"); } protIO(io); } //************************************************* //* XML_N * //************************************************* XML_N::XML_N( const XML_N &nd ) : mName("nd"), mText(""), mParent(NULL) { this->operator=(nd); } XML_N &XML_N::operator=( const XML_N &prm ) { //Delete self children and attributes mAttr.clear(); for(unsigned i_ch = 0; i_ch < mChildren.size(); i_ch++) delete mChildren[i_ch]; mChildren.clear(); //Copy params (name,text, attributes and instructions) mName = prm.mName; mText = prm.mText; vector ls; prm.attrList(ls); for(unsigned i_a = 0; i_a < ls.size(); i_a++) setAttr(ls[i_a],prm.attr(ls[i_a])); //Recursive copy children for(unsigned i_ch = 0; i_ch < prm.childSize(); i_ch++) *childAdd() = *prm.childGet(i_ch); return *this; } void XML_N::childAdd( XML_N *n ) { if(!n) return; mChildren.push_back(n); n->mParent = this; } XML_N* XML_N::childAdd( const string &name ) { XML_N *n = new XML_N(name); childAdd(n); return n; } void XML_N::childDel( const unsigned id ) { if(id >= childSize()) throw Error("Child %d is not present.", id); delete mChildren[id]; mChildren.erase(mChildren.begin()+id); } void XML_N::childDel( XML_N *nd ) { for(unsigned i_ch = 0; i_ch < mChildren.size(); i_ch++) if(mChildren[i_ch] == nd) { delete mChildren[i_ch]; mChildren.erase(mChildren.begin()+i_ch); break; } } void XML_N::childClear( const string &name ) { for(unsigned i_ch = 0; i_ch < mChildren.size(); ) if(name.empty() || mChildren[i_ch]->name() == name) childDel(i_ch); else i_ch++; } int XML_N::childIns( unsigned id, XML_N * n ) { if(!n) return -1; if(id > childSize()) id = childSize(); mChildren.insert(mChildren.begin()+id, n); n->mParent = this; return id; } XML_N* XML_N::childIns( unsigned id, const string &name ) { XML_N *n = new XML_N( name ); childIns(id,n); return n; } XML_N* XML_N::childGet( const int index, bool noex ) const { if(index >= 0 && index < (int)childSize()) return mChildren[index]; if(noex) return NULL; throw Error("Child %d is not present.", index); } XML_N* XML_N::childGet( const string &name, const int numb, bool noex ) const { for(int i_ch = 0, i_n = 0; i_ch < (int)childSize(); i_ch++) if(strcasecmp(childGet(i_ch)->name().c_str(),name.c_str()) == 0 && i_n++ == numb) return childGet(i_ch); if(noex) return NULL; throw Error("Child %s:%d is not found!", name.c_str(), numb); } XML_N* XML_N::childGet( const string &attr, const string &val, bool noex ) const { for(unsigned i_f = 0; i_f < childSize(); i_f++) if(childGet(i_f)->attr(attr) == val) return childGet(i_f); if(noex) return NULL; throw Error("Child with attribut %s=%s is not present.", attr.c_str(), val.c_str()); } XML_N* XML_N::getElementBy( const string &iattr, const string &val ) { if(attr(iattr) == val) return this; XML_N* rez = NULL; for(unsigned i_ch = 0; !rez && i_ch < childSize(); i_ch++) rez = childGet(i_ch)->getElementBy(iattr,val); return rez; } XML_N* XML_N::root( ) { XML_N *cur = this; while(cur->parent()) cur = cur->parent(); return cur; } string XML_N::text( bool childs, bool recursive ) const { if(!childs || mName == "<*>") return mText; string rez; for(unsigned i_f = 0; i_f < childSize(); i_f++) if(childGet(i_f)->name() == "<*>") rez += childGet(i_f)->text(); else if(recursive) rez += childGet(i_f)->text(childs, recursive); return rez; } XML_N* XML_N::setText( const string &s, bool childs ) { if(!childs || mName == "<*>") { mText = s; return this; } int i_ch = -1; for(int i_f = 0; i_f < (int)childSize(); i_f++) if(childGet(i_f)->name() == "<*>") { if(i_ch < 0) childGet(i_f)->mText = s; else childDel(i_f--); i_ch = i_f; } if(i_ch < 0) childAdd("<*>")->mText = s; return this; } void XML_N::attrList( vector & list ) const { list.clear(); for(unsigned i_a = 0; i_a < mAttr.size(); i_a++) list.push_back(mAttr[i_a].first); } XML_N* XML_N::attrDel( const string &name ) { for(unsigned i_a = 0; i_a < mAttr.size(); i_a++) if(mAttr[i_a].first == name) { mAttr.erase(mAttr.begin()+i_a); break; } return this; } void XML_N::attrClear( ) { mAttr.clear(); } string XML_N::attr( const string &name, bool caseSens ) const { if(caseSens) { for(unsigned i_a = 0; i_a < mAttr.size(); i_a++) if(mAttr[i_a].first == name) return mAttr[i_a].second; } else for(unsigned i_a = 0; i_a < mAttr.size(); i_a++) if(strcasecmp(mAttr[i_a].first.c_str(),name.c_str()) == 0) return mAttr[i_a].second; return ""; } XML_N* XML_N::setAttr( const string &name, const string &val ) { for(unsigned i_a = 0; i_a < mAttr.size(); i_a++) if(mAttr[i_a].first == name) { mAttr[i_a].second = val; return this; } mAttr.push_back(pair(name,val)); return this; } XML_N* XML_N::clear() { attrClear(); mText.clear(); childClear(); return this; } //************************************************* //* Error * //************************************************* Error::Error( const char *fmt, ... ) { char str[1024]; //!!!! va_list argptr; va_start(argptr,fmt); vsnprintf(str,sizeof(str),fmt,argptr); va_end(argptr); cod = 0; mess = str; } Error::Error( int icod, const char *fmt, ... ) { char str[1024]; //!!!! va_list argptr; va_start(argptr,fmt); vsnprintf(str,sizeof(str),fmt,argptr); va_end(argptr); cod = icod; mess = str; } }