/** * sigtran.cpp * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Signalling Stack - implements the support for SS7, ISDN and PSTN * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2013 Null Team * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * 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. */ #include "yatesig.h" #include #define MAX_UNACK 256 #define AVG_DELAY 100 using namespace TelEngine; #define MAKE_NAME(x) { #x, SIGTRAN::x } static const TokenDict s_classes[] = { // this list must be kept in synch with the header MAKE_NAME(MGMT), MAKE_NAME(TRAN), MAKE_NAME(SSNM), MAKE_NAME(ASPSM), MAKE_NAME(ASPTM), MAKE_NAME(QPTM), MAKE_NAME(MAUP), MAKE_NAME(CLMSG), MAKE_NAME(COMSG), MAKE_NAME(RKM), MAKE_NAME(IIM), MAKE_NAME(M2PA), { 0, 0 } }; #undef MAKE_NAME #define MAKE_NAME(x) { #x, SIGTRAN::Mgmt##x } static const TokenDict s_mgmt_types[] = { MAKE_NAME(ERR), MAKE_NAME(NTFY), { 0, 0 } }; #undef MAKE_NAME #define MAKE_NAME(x) { #x, SIGTRAN::Ssnm##x } static const TokenDict s_ssnm_types[] = { MAKE_NAME(DUNA), MAKE_NAME(DAVA), MAKE_NAME(DAUD), MAKE_NAME(SCON), MAKE_NAME(DUPU), MAKE_NAME(DRST), { 0, 0 } }; #undef MAKE_NAME #define MAKE_NAME(x) { #x, SIGTRAN::Aspsm##x } static const TokenDict s_aspsm_types[] = { MAKE_NAME(UP), MAKE_NAME(DOWN), MAKE_NAME(BEAT), MAKE_NAME(UP_ACK), MAKE_NAME(DOWN_ACK), MAKE_NAME(BEAT_ACK), { 0, 0 } }; #undef MAKE_NAME #define MAKE_NAME(x) { #x, SIGTRAN::Asptm##x } static const TokenDict s_asptm_types[] = { MAKE_NAME(ACTIVE), MAKE_NAME(INACTIVE), MAKE_NAME(ACTIVE_ACK), MAKE_NAME(INACTIVE_ACK), { 0, 0 } }; #undef MAKE_NAME #define MAKE_NAME(x) { #x, SIGTRAN::Rkm##x } static const TokenDict s_rkm_types[] = { MAKE_NAME(REG_REQ), MAKE_NAME(REG_RSP), MAKE_NAME(DEREG_REQ), MAKE_NAME(DEREG_RSP), { 0, 0 } }; #undef MAKE_NAME #define MAKE_NAME(x) { #x, SIGTRAN::Iim##x } static const TokenDict s_iim_types[] = { MAKE_NAME(REG_REQ), MAKE_NAME(REG_RSP), MAKE_NAME(DEREG_REQ), MAKE_NAME(DEREG_RSP), { 0, 0 } }; #undef MAKE_NAME #define MAKE_NAME(x) { #x, SS7M2PA::x } static TokenDict s_m2pa_types[] = { MAKE_NAME(UserData), MAKE_NAME(LinkStatus), { 0, 0 } }; #undef MAKE_NAME const TokenDict* SIGTRAN::classNames() { return s_classes; } const char* SIGTRAN::typeName(unsigned char msgClass, unsigned char msgType, const char* defValue) { switch (msgClass) { case MGMT: return lookup(msgType,s_mgmt_types,defValue); case SSNM: return lookup(msgType,s_ssnm_types,defValue); case ASPSM: return lookup(msgType,s_aspsm_types,defValue); case ASPTM: return lookup(msgType,s_asptm_types,defValue); case RKM: return lookup(msgType,s_rkm_types,defValue); case IIM: return lookup(msgType,s_iim_types,defValue); case M2PA: return lookup(msgType,s_m2pa_types,defValue); default: return defValue; } } SIGTRAN::SIGTRAN(u_int32_t payload, u_int16_t port) : m_trans(0), m_payload(payload), m_defPort(port), m_transMutex(false,"SIGTRAN::transport") { } SIGTRAN::~SIGTRAN() { attach(0); } // Check if a stream in the transport is connected bool SIGTRAN::connected(int streamId) const { m_transMutex.lock(); RefPointer trans = m_trans; m_transMutex.unlock(); return trans && trans->connected(streamId); } // Attach a transport to the SIGTRAN instance void SIGTRAN::attach(SIGTransport* trans) { Lock lock(m_transMutex); if (trans == m_trans) return; if (!(trans && trans->ref())) trans = 0; SIGTransport* tmp = m_trans; m_trans = trans; lock.drop(); if (tmp) { tmp->attach(0); tmp->destruct(); } if (trans) { trans->attach(this); SignallingEngine* engine = SignallingEngine::self(); if (engine) engine->insert(trans); trans->deref(); } } // Transmit a SIGTRAN message over the attached transport bool SIGTRAN::transmitMSG(unsigned char msgVersion, unsigned char msgClass, unsigned char msgType, const DataBlock& msg, int streamId) const { m_transMutex.lock(); RefPointer trans = m_trans; m_transMutex.unlock(); return trans && trans->transmitMSG(msgVersion,msgClass,msgType,msg,streamId); } bool SIGTRAN::restart(bool force) { m_transMutex.lock(); RefPointer trans = m_trans; m_transMutex.unlock(); if (!trans) return false; trans->reconnect(force); return true; } bool SIGTRAN::getSocketParams(const String& params, NamedList& result) { m_transMutex.lock(); RefPointer trans = m_trans; m_transMutex.unlock(); if (!trans) return false; trans->getSocketParams(params,result); return true; } bool SIGTRAN::hasTransportThread() { m_transMutex.lock(); RefPointer trans = m_trans; m_transMutex.unlock(); if (!trans) return false; return trans->hasThread(); } void SIGTRAN::stopTransportThread() { m_transMutex.lock(); RefPointer trans = m_trans; m_transMutex.unlock(); if (trans) trans->stopThread(); } // Attach or detach an user adaptation layer void SIGTransport::attach(SIGTRAN* sigtran) { if (m_sigtran != sigtran) { m_sigtran = sigtran; attached(sigtran != 0); } } // Retrieve the default port to use u_int32_t SIGTransport::defPort() const { return m_sigtran ? m_sigtran->defPort() : 0; } // Request processing from the adaptation layer bool SIGTransport::processMSG(unsigned char msgVersion, unsigned char msgClass, unsigned char msgType, const DataBlock& msg, int streamId) const { XDebug(this,DebugAll,"Received message class %s type %s (0x%02X) on stream %d", lookup(msgClass,s_classes,"Unknown"), SIGTRAN::typeName(msgClass,msgType,"Unknown"),msgType,streamId); return alive() && m_sigtran && m_sigtran->processMSG(msgVersion,msgClass,msgType,msg,streamId); } void SIGTransport::notifyLayer(SignallingInterface::Notification event) { if (alive() && m_sigtran) m_sigtran->notifyLayer(event); } // Build the common header and transmit a message to the network bool SIGTransport::transmitMSG(unsigned char msgVersion, unsigned char msgClass, unsigned char msgType, const DataBlock& msg, int streamId) { if (!alive()) return false; XDebug(this,DebugAll,"Sending message class %s type %s (0x%02X) on stream %d", lookup(msgClass,s_classes,"Unknown"), SIGTRAN::typeName(msgClass,msgType,"Unknown"),msgType,streamId); if (!connected(streamId)) { Debug(this,DebugMild,"Cannot send message, stream %d not connected [%p]", streamId,this); return false; } unsigned char hdr[8]; unsigned int len = 8 + msg.length(); hdr[0] = msgVersion; hdr[1] = 0; hdr[2] = msgClass; hdr[3] = msgType; hdr[4] = 0xff & (len >> 24); hdr[5] = 0xff & (len >> 16); hdr[6] = 0xff & (len >> 8); hdr[7] = 0xff & len; DataBlock header(hdr,8,false); bool ok = transmitMSG(header,msg,streamId); header.clear(false); return ok; } bool SIGTransport::transportNotify(SIGTransport* newTransport, const SocketAddr& addr) { if (alive() && m_sigtran) { return m_sigtran->transportNotify(newTransport,addr); } TelEngine::destruct(newTransport); return false; } /** * Class SIGAdaptation */ SIGAdaptation::SIGAdaptation(const char* name, const NamedList* params, u_int32_t payload, u_int16_t port) : SignallingComponent(name,params), SIGTRAN(payload,port), Mutex(true,"SIGAdaptation"), m_maxRetransmit(1000), m_sendHeartbeat(0), m_waitHeartbeatAck(0) { DDebug(this,DebugAll,"Creating SIGTRAN UA [%p]",this); for (int i = 0; i < 32;i++) m_streamsHB[i] = HeartbeatDisabled; if (params) { m_waitHeartbeatAck.interval(*params,"wait_hb_ack",500,2000,false); m_sendHeartbeat.interval(*params,"send_hb",15000,30000,true); // The maximum interval in miliseconds allowed for SCTP to retransmit // a lost package m_maxRetransmit = params->getIntValue("max_interval_retrans",1000); } } SIGAdaptation::~SIGAdaptation() { DDebug(this,DebugAll,"Destroying SIGTRAN UA [%p]",this); } bool SIGAdaptation::initialize(const NamedList* config) { if (transport()) return true; NamedList params(""); if (resolveConfig(YSTRING("sig"),params,config) || resolveConfig(YSTRING("basename"),params,config)) { DDebug(this,DebugInfo,"Creating transport for SIGTRAN UA [%p]",this); params.addParam("basename",params); SIGTransport* tr = YSIGCREATE(SIGTransport,¶ms); if (!tr) return false; SIGTRAN::attach(tr); if (tr->initialize(¶ms)) return true; SIGTRAN::attach(0); } return false; } void SIGAdaptation::notifyLayer(SignallingInterface::Notification status) { Lock myLock(this); if (status != SignallingInterface::LinkUp) { m_waitHeartbeatAck.stop(); m_sendHeartbeat.stop(); for (int i = 0;i < 32;i++) { if (m_streamsHB[i] == HeartbeatDisabled) continue; m_streamsHB[i] = HeartbeatEnabled; } return; } m_sendHeartbeat.start(); String params = "rto_max"; NamedList result("sctp_params"); if (getSocketParams(params,result)) { int rtoMax = result.getIntValue(YSTRING("rto_max")); unsigned int maxRetrans = rtoMax + AVG_DELAY; if (maxRetrans > m_maxRetransmit) { Debug(this,DebugConf, "%s! Maximum SCTP interval to retransmit a packet is: %d, maximum allowed is: %d ", "The SCTP configuration timers are unreliable", maxRetrans,m_maxRetransmit); } } else Debug(this,DebugNote,"Failed to obtain socket params"); } // Process common (MGMT, ASPSM, ASPTM) messages bool SIGAdaptation::processCommonMSG(unsigned char msgClass, unsigned char msgType, const DataBlock& msg, int streamId) { switch (msgClass) { case MGMT: return processMgmtMSG(msgType,msg,streamId); case ASPSM: if (msgType == AspsmBEAT || msgType == AspsmBEAT_ACK) return processHeartbeat(msgType,msg,streamId); return processAspsmMSG(msgType,msg,streamId); case ASPTM: return processAsptmMSG(msgType,msg,streamId); default: Debug(this,DebugWarn,"Unsupported message class 0x%02X",msgClass); return false; } } bool SIGAdaptation::processHeartbeat(unsigned char msgType, const DataBlock& msg, int streamId) { XDebug(this,DebugAll,"Received %s in stream %d",lookup(msgType,s_aspsm_types),streamId); if (msgType == AspsmBEAT) return transmitMSG(ASPSM,AspsmBEAT_ACK,msg,streamId); if (msgType != AspsmBEAT_ACK || streamId > 32) return false; Lock myLock(this); // Mark the first stream witch waits to receive heartbeat // Do not mark the received stream because some implementations may send // heartbeat responses only on stream 0. for (int i = 0;i < 32;i++) { if (m_streamsHB[i] == HeartbeatWaitResponse) { m_streamsHB[i] = HeartbeatEnabled; return true; } } return false; } // Advance to next tag in a message bool SIGAdaptation::nextTag(const DataBlock& data, int& offset, uint16_t& tag, uint16_t& length) { unsigned int offs = (offset < 0) ? 0 : offset; unsigned char* ptr = data.data(offs,4); if (!ptr) return false; unsigned int len = ((uint16_t)ptr[2] << 8) | ptr[3]; if (len < 4) return false; if (offset >= 0) { // Skip over current parameter offs += (len + 3) & ~3; ptr = data.data(offs,4); if (!ptr) return false; len = ((uint16_t)ptr[2] << 8) | ptr[3]; if (len < 4) return false; } if ((offs + len) > data.length()) return false; offset = offs; tag = ((uint16_t)ptr[0] << 8) | ptr[1]; length = len - 4; return true; } // Find a specific tag in a message bool SIGAdaptation::findTag(const DataBlock& data, int& offset, uint16_t tag, uint16_t& length) { int offs = -1; uint16_t type = 0; uint16_t len = 0; while (nextTag(data,offs,type,len)) { if (type == tag) { offset = offs; length = len; return true; } } return false; } // Get a 32 bit integer parameter bool SIGAdaptation::getTag(const DataBlock& data, uint16_t tag, uint32_t& value) { int offs = -1; uint16_t len = 0; if (findTag(data,offs,tag,len) && (4 == len)) { value = data.at(offs + 4) << 24 | data.at(offs + 5) << 16 | data.at(offs + 6) << 8 | data.at(offs + 7); return true; } return false; } // Get a string parameter bool SIGAdaptation::getTag(const DataBlock& data, uint16_t tag, String& value) { int offs = -1; uint16_t len = 0; if (findTag(data,offs,tag,len)) { value.assign((char*)data.data(offs + 4),len); return true; } return false; } // Get a raw binary parameter bool SIGAdaptation::getTag(const DataBlock& data, uint16_t tag, DataBlock& value) { int offs = -1; uint16_t len = 0; if (findTag(data,offs,tag,len)) { value.assign(data.data(offs + 4),len); return true; } return false; } // Add a 32 bit integer parameter void SIGAdaptation::addTag(DataBlock& data, uint16_t tag, uint32_t value) { unsigned char buf[8]; buf[0] = tag >> 8; buf[1] = tag & 0xff; buf[2] = 0; buf[3] = 8; buf[4] = (value >> 24) & 0xff; buf[5] = (value >> 16) & 0xff; buf[6] = (value >> 8) & 0xff; buf[7] = value & 0xff; DataBlock tmp(buf,8,false); data += tmp; tmp.clear(false); } // Add a string parameter void SIGAdaptation::addTag(DataBlock& data, uint16_t tag, const String& value) { unsigned int len = value.length() + 4; if (len > 32768) return; unsigned char buf[4]; buf[0] = tag >> 8; buf[1] = tag & 0xff; buf[2] = (len >> 8) & 0xff; buf[3] = len & 0xff; DataBlock tmp(buf,4,false); data += tmp; data += value; tmp.clear(false); len = (len & 3); if (len) { buf[0] = buf[1] = buf[2] = 0; tmp.assign(buf,4 - len,false); data += tmp; tmp.clear(false); } } // Add a raw binary parameter void SIGAdaptation::addTag(DataBlock& data, uint16_t tag, const DataBlock& value) { unsigned int len = value.length() + 4; if (len > 32768) return; unsigned char buf[4]; buf[0] = tag >> 8; buf[1] = tag & 0xff; buf[2] = (len >> 8) & 0xff; buf[3] = len & 0xff; DataBlock tmp(buf,4,false); data += tmp; data += value; tmp.clear(false); len = (len & 3); if (len) { buf[0] = buf[1] = buf[2] = 0; tmp.assign(buf,4 - len,false); data += tmp; tmp.clear(false); } } void SIGAdaptation::timerTick(const Time& when) { if (m_sendHeartbeat.timeout()) { m_sendHeartbeat.stop(); Lock myLock(this); DataBlock data; for (int i = 0; i < 32; i++) { if (m_streamsHB[i] == HeartbeatDisabled) continue; transmitMSG(ASPSM,AspsmBEAT,data,i); m_streamsHB[i] = HeartbeatWaitResponse; } m_waitHeartbeatAck.start(); } if (m_waitHeartbeatAck.timeout()) { m_waitHeartbeatAck.stop(); Lock myLock(this); for (int i = 0;i < 32;i++) { if (m_streamsHB[i] == HeartbeatWaitResponse) { // The stream is freezed Debug(this,DebugWarn, "Stream %d is freezed! Restarting transport",i); restart(true); return; } } m_sendHeartbeat.start(); } } /** * Class SIGAdaptClient */ #define MAKE_NAME(x) { #x, SIGAdaptClient::x } static const TokenDict s_clientStates[] = { MAKE_NAME(AspDown), MAKE_NAME(AspUpRq), MAKE_NAME(AspUp), MAKE_NAME(AspActRq), MAKE_NAME(AspActive), { 0, 0 } }; #undef MAKE_NAME static const TokenDict s_uaErrors[] = { { "Invalid Version", SIGAdaptation::InvalidVersion }, { "Invalid Interface Identifier", SIGAdaptation::InvalidIID }, { "Unsupported Message Class", SIGAdaptation::UnsupportedMessageClass }, { "Unsupported Message Type", SIGAdaptation::UnsupportedMessageType }, { "Unsupported Traffic Handling Mode", SIGAdaptation::UnsupportedTrafficMode }, { "Unexpected Message", SIGAdaptation::UnexpectedMessage }, { "Protocol Error", SIGAdaptation::ProtocolError }, { "Unsupported Interface Identifier Type", SIGAdaptation::UnsupportedIIDType }, { "Invalid Stream Identifier", SIGAdaptation::InvalidStreamIdentifier }, { "Unassigned TEI", SIGAdaptation::UnassignedTEI }, { "Unrecognized SAPI", SIGAdaptation::UnrecognizedSAPI }, { "Invalid TEI, SAPI combination", SIGAdaptation::InvalidTEISAPI }, { "Refused - Management Blocking", SIGAdaptation::ManagementBlocking }, { "ASP Identifier Required", SIGAdaptation::ASPIDRequired }, { "Invalid ASP Identifier", SIGAdaptation::InvalidASPID }, { "ASP Active for Interface Identifier(s)", SIGAdaptation::ASPActiveIID }, { "Invalid Parameter Value ", SIGAdaptation::InvalidParameterValue }, { "Parameter Field Error", SIGAdaptation::ParameterFieldError }, { "Unexpected Parameter", SIGAdaptation::UnexpectedParameter }, { "Destination Status Unknown", SIGAdaptation::DestinationStatusUnknown }, { "Invalid Network Appearance", SIGAdaptation::InvalidNetworkAppearance }, { "Missing Parameter", SIGAdaptation::MissingParameter }, { "Invalid Routing Context", SIGAdaptation::InvalidRoutingContext }, { "No Configured AS for ASP", SIGAdaptation::NotConfiguredAS }, { "Subsystem Status Unknown", SIGAdaptation::SubsystemStatusUnknown }, { "Invalid loadsharing label", SIGAdaptation::InvalidLoadsharingLabel }, { 0, 0 } }; static const TokenDict s_trafficModes[] = { { "unused", SIGAdaptation::TrafficUnused }, { "override", SIGAdaptation::TrafficOverride }, { "loadshare", SIGAdaptation::TrafficLoadShare }, { "broadcast", SIGAdaptation::TrafficBroadcast }, { 0, 0 } }; // Helper storage object typedef GenPointer AdaptUserPtr; // Constructor SIGAdaptClient::SIGAdaptClient(const char* name, const NamedList* params, u_int32_t payload, u_int16_t port) : SIGAdaptation(name,params,payload,port), m_aspId(-1), m_traffic(TrafficOverride), m_state(AspDown) { if (params) { #ifdef DEBUG String tmp; if (debugAt(DebugAll)) params->dump(tmp,"\r\n ",'\'',true); Debug(this,DebugInfo,"SIGAdaptClient(%u,%u) created [%p]%s", payload,port,this,tmp.c_str()); #endif m_aspId = params->getIntValue(YSTRING("aspid"),m_aspId); m_traffic = (TrafficMode)params->getIntValue(YSTRING("traffic"),s_trafficModes,m_traffic); } // Enable heartbeat on stream 0; because is unlikely to have a adapt user // who uses stream 0 enableHeartbeat(0); } // Attach one user entity to the ASP void SIGAdaptClient::attach(SIGAdaptUser* user) { if (!user) return; Lock mylock(this); m_users.append(new AdaptUserPtr(user)); // Enable heartbeat on users stream id enableHeartbeat(user->getStreamId()); } // Detach one user entity from the ASP void SIGAdaptClient::detach(SIGAdaptUser* user) { if (!user) return; Lock mylock(this); for (ObjList* o = m_users.skipNull(); o; o = o->skipNext()) { AdaptUserPtr* p = static_cast(o->get()); if (*p != user) continue; m_users.remove(p,false); if (!m_users.count()) { setState(AspDown,false); transmitMSG(ASPSM,AspsmDOWN,DataBlock::empty()); } return; } // Reset all heartbeat streams resetHeartbeat(); enableHeartbeat(0); for (ObjList* o = m_users.skipNull(); o; o = o->skipNext()) { AdaptUserPtr* p = static_cast(o->get()); enableHeartbeat((*p)->getStreamId()); } } // Status notification from transport layer void SIGAdaptClient::notifyLayer(SignallingInterface::Notification status) { SIGAdaptation::notifyLayer(status); switch (status) { case SignallingInterface::LinkDown: case SignallingInterface::HardwareError: switch (m_state) { case AspDown: case AspUpRq: break; default: setState(AspUpRq); } break; case SignallingInterface::LinkUp: if (m_state >= AspUpRq) { setState(AspUpRq,false); DataBlock data; if (m_aspId != -1) addTag(data,0x0011,m_aspId); transmitMSG(ASPSM,AspsmUP,data); } break; default: return; } } // Request activation of the ASP bool SIGAdaptClient::activate() { Lock mylock(this); if (m_state >= AspActRq) return true; if (!transport()) return false; switch (m_state) { case AspUpRq: return true; case AspDown: setState(AspUpRq,false); { DataBlock data; if (m_aspId != -1) addTag(data,0x0011,m_aspId); mylock.drop(); transmitMSG(ASPSM,AspsmUP,data); return true; } case AspUp: setState(AspActRq,false); { DataBlock data; if (m_traffic != TrafficUnused) addTag(data,0x000b,m_traffic); mylock.drop(); return transmitMSG(ASPTM,AsptmACTIVE,data,1); } default: return false; } } // Change the state of the ASP void SIGAdaptClient::setState(AspState state, bool notify) { Lock mylock(this); if (state == m_state) return; Debug(this,DebugAll,"ASP state change: %s -> %s [%p]", lookup(m_state,s_clientStates,"?"),lookup(state,s_clientStates,"?"),this); bool up = aspUp(); bool act = aspActive(); m_state = state; if (!notify) return; if (act != aspActive()) activeChange(aspActive()); else if (aspUp() && !up) { setState(AspActRq,false); DataBlock data; if (m_traffic != TrafficUnused) addTag(data,0x000b,m_traffic); transmitMSG(ASPTM,AsptmACTIVE,data,1); } } // Notification of activity state change void SIGAdaptClient::activeChange(bool active) { Debug(this,DebugNote,"ASP traffic is now %s [%p]", (active ? "active" : "inactive"),this); Lock mylock(this); for (ObjList* o = m_users.skipNull(); o; o = o->skipNext()) { AdaptUserPtr* p = static_cast(o->get()); (*p)->activeChange(active); } } // Process common MGMT messages bool SIGAdaptClient::processMgmtMSG(unsigned char msgType, const DataBlock& msg, int streamId) { switch (msgType) { case SIGTRAN::MgmtERR: { u_int32_t errCode = 0; if (SIGAdaptation::getTag(msg,0x000c,errCode)) { switch (errCode) { case 1: Debug(this,DebugWarn,"SG Reported invalid version"); setState(AspDown); return true; case 5: Debug(this,DebugWarn,"SG Reported invalid traffic mode %s", lookup(m_traffic,s_trafficModes,"Unknown")); setState(AspDown); return true; case 14: Debug(this,DebugWarn,"SG Reported ASP ID required"); setState(AspDown); return true; case 15: Debug(this,DebugWarn,"SG Reported invalid ASP id=%d",m_aspId); setState(AspDown); return true; default: Debug(this,DebugWarn,"SG reported error %u: %s",errCode,lookup(errCode,s_uaErrors,"Unknown")); return true; } } } break; case SIGTRAN::MgmtNTFY: { u_int32_t status = 0; if (SIGAdaptation::getTag(msg,0x000d,status)) { const char* our = ""; if (m_aspId != -1) { our = "Some "; u_int32_t aspid = 0; if (SIGAdaptation::getTag(msg,0x0011,aspid)) our = ((int32_t)aspid == m_aspId) ? "Our " : "Other "; } switch (status >> 16) { case 1: Debug(this,DebugInfo,"%sASP State Change: %u",our,status & 0xffff); return true; case 2: Debug(this,DebugInfo,"%sASP State Info: %u",our,status & 0xffff); return true; } } } break; } Debug(this,DebugStub,"Please handle ASP message %u class MGMT",msgType); return false; } // Process common ASPSM messages bool SIGAdaptClient::processAspsmMSG(unsigned char msgType, const DataBlock& msg, int streamId) { switch (msgType) { case AspsmUP_ACK: setState(AspUp); return true; case AspsmDOWN_ACK: setState(AspDown); return true; case AspsmUP: case AspsmDOWN: Debug(this,DebugWarn,"Wrong direction for ASPSM %s ASP message!", SIGTRAN::typeName(ASPSM,msgType)); return false; } Debug(this,DebugStub,"Please handle ASP message %u class ASPSM",msgType); return false; } // Process common ASPTM messages bool SIGAdaptClient::processAsptmMSG(unsigned char msgType, const DataBlock& msg, int streamId) { switch (msgType) { case AsptmACTIVE_ACK: setState(AspActive); return true; case AsptmINACTIVE_ACK: if (aspUp()) setState(AspUp); return true; case AsptmACTIVE: case AsptmINACTIVE: Debug(this,DebugWarn,"Wrong direction for ASPTM %s ASP message!", SIGTRAN::typeName(ASPTM,msgType)); return false; } Debug(this,DebugStub,"Please handle ASP message %u class ASPTM",msgType); return false; } /** * Class SIGAdaptServer */ // Process common MGMT messages bool SIGAdaptServer::processMgmtMSG(unsigned char msgType, const DataBlock& msg, int streamId) { Debug(this,DebugStub,"Please handle SG message %u class MGMT",msgType); return false; } // Process common ASPSM messages bool SIGAdaptServer::processAspsmMSG(unsigned char msgType, const DataBlock& msg, int streamId) { switch (msgType) { case AspsmUP: case AspsmDOWN: break; case AspsmUP_ACK: case AspsmDOWN_ACK: Debug(this,DebugWarn,"Wrong direction for ASPSM %s SG message!", SIGTRAN::typeName(ASPSM,msgType)); return false; } Debug(this,DebugStub,"Please handle SG message %u class ASPSM",msgType); return false; } // Process common ASPTM messages bool SIGAdaptServer::processAsptmMSG(unsigned char msgType, const DataBlock& msg, int streamId) { switch (msgType) { case AsptmACTIVE: case AsptmINACTIVE: break; case AsptmACTIVE_ACK: case AsptmINACTIVE_ACK: Debug(this,DebugWarn,"Wrong direction for ASPTM %s SG message!", SIGTRAN::typeName(ASPTM,msgType)); return false; } Debug(this,DebugStub,"Please handle SG message %u class ASPTM",msgType); return false; } /** * Class SIGAdaptUser */ SIGAdaptUser::~SIGAdaptUser() { adaptation(0); } // Attach an ASP CLient to this user, detach old client void SIGAdaptUser::adaptation(SIGAdaptClient* adapt) { if (adapt == m_adaptation) return; if (m_adaptation) { m_adaptation->detach(this); TelEngine::destruct(m_adaptation); } m_adaptation = adapt; if (adapt && adapt->ref()) adapt->attach(this); } /** * Class SS7M2PA */ static TokenDict s_state[] = { {"Alignment", SS7M2PA::Alignment}, {"ProvingNormal", SS7M2PA::ProvingNormal}, {"ProvingEmergency", SS7M2PA::ProvingEmergency}, {"Ready", SS7M2PA::Ready}, {"ProcessorOutage", SS7M2PA::ProcessorOutage}, {"ProcessorRecovered", SS7M2PA::ProcessorRecovered}, {"Busy", SS7M2PA::Busy}, {"BusyEnded", SS7M2PA::BusyEnded}, {"OutOfService", SS7M2PA::OutOfService}, {0,0} }; static const TokenDict s_m2pa_dict_control[] = { { "pause", SS7M2PA::Pause }, { "resume", SS7M2PA::Resume }, { "align", SS7M2PA::Align }, { "transport_restart", SS7M2PA::TransRestart }, { 0, 0 } }; SS7M2PA::SS7M2PA(const NamedList& params) : SignallingComponent(params.safe("SS7M2PA"),¶ms,"ss7-m2pa"), SIGTRAN(5,3565), m_seqNr(0xffffff), m_needToAck(0xffffff), m_lastAck(0xffffff), m_maxQueueSize(MAX_UNACK), m_localStatus(OutOfService), m_state(OutOfService), m_remoteStatus(OutOfService), m_transportState(Idle), m_connFailCounter(0), m_connFailThreshold(0), m_mutex(true,"SS7M2PA"), m_t1(0), m_t2(0), m_t3(0), m_t4(0), m_ackTimer(0), m_confTimer(0), m_oosTimer(0),m_waitOosTimer(0), m_connFailTimer(0), m_autostart(false), m_sequenced(false), m_dumpMsg(false) { // Alignment ready timer ~45s m_t1.interval(params,"t1",45000,50000,false); // Not Aligned timer ~5s m_t2.interval(params,"t2",5000,5500,false); // Aligned timer ~1s m_t3.interval(params,"t3",1000,1500,false); // Proving timer Normal ~8s, Emergency ~0.5s m_t4.interval(params,"t4",500,8000,false); // Acknowledge timer ~1s m_ackTimer.interval(params,"ack_timer",1000,1100,false); // Confirmation timer 1/2 t4 m_confTimer.interval(params,"conf_timer",50,150,false); // Out of service timer m_oosTimer.interval(params,"oos_timer",3000,5000,false); m_waitOosTimer.interval(params,"wait_oos",500,1000,false); m_connFailTimer.interval(params,"conn_test",50000,300000,false); m_connFailThreshold = params.getIntValue(YSTRING("conn_threshold"),3); m_sequenced = params.getBoolValue(YSTRING("sequenced"),false); // Maximum unacknowledged messages, max_unack+1 will force an ACK m_maxUnack = params.getIntValue(YSTRING("max_unack"),4); if (m_maxUnack > 10) m_maxUnack = 10; m_maxQueueSize = params.getIntValue(YSTRING("max_queue_size"),MAX_UNACK); if (m_maxQueueSize < 16) m_maxQueueSize = 16; if (m_maxQueueSize > 65356) m_maxQueueSize = 65356; DDebug(this,DebugAll,"Creating SS7M2PA [%p]",this); } SS7M2PA::~SS7M2PA() { Lock lock(m_mutex); m_ackList.clear(); DDebug(this,DebugAll,"Destroying SS7M2PA [%p]",this); } void SS7M2PA::destroyed() { stopTransportThread(); SIGTRAN::attach(0); SS7Layer2::destroyed(); } bool SS7M2PA::initialize(const NamedList* config) { #ifdef DEBUG String tmp; if (config && debugAt(DebugAll)) config->dump(tmp,"\r\n ",'\'',true); Debug(this,DebugInfo,"SS7M2PA::initialize(%p) [%p]%s",config,this,tmp.c_str()); #endif m_dumpMsg = config && config->getBoolValue(YSTRING("dumpMsg"),false); m_autostart = !config || config->getBoolValue(YSTRING("autostart"),true); m_autoEmergency = !config || config->getBoolValue(YSTRING("autoemergency"),true); if (config && !transport()) { NamedList params(""); if (resolveConfig(YSTRING("sig"),params,config) || resolveConfig(YSTRING("basename"),params,config)) { params.addParam("basename",params); params.addParam("protocol","ss7"); params.addParam("listen-notify","false"); SIGTransport* tr = YSIGCREATE(SIGTransport,¶ms); if (!tr) return false; SIGTRAN::attach(tr); if (!tr->initialize(¶ms)) SIGTRAN::attach(0); m_sequenced = config->getBoolValue(YSTRING("sequenced"),transport() ? transport()->reliable() : false); } } return transport() && control(Resume,const_cast(config)); } void SS7M2PA::dumpMsg(u_int8_t version, u_int8_t mClass, u_int8_t type, const DataBlock& data, int stream, bool send) { String dump = "SS7M2PA "; dump << (send ? "Sending:" : "Received:"); dump << "\r\n-----"; String indent = "\r\n "; dump << indent << "Version: " << version; dump << " " << "Message class: " << mClass; dump << " " << "Message type: " << lookup(type,s_m2pa_types,"Unknown"); dump << indent << "Stream: " << stream; if (data.length() >= 8) { u_int32_t bsn = (data[1] << 16) | (data[2] << 8) | data[3]; u_int32_t fsn = (data[5] << 16) | (data[6] << 8) | data[7]; dump << indent << "FSN : " << fsn << " BSN: " << bsn; if (type == LinkStatus) { u_int32_t status = (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11]; dump << indent << "Status: " << lookup(status,s_state); } else { String hex; hex.hexify((u_int8_t*)data.data() + 8,data.length() - 8,' '); dump << indent << "Data: " << hex; } } dump << "\r\n-----"; Debug(this,DebugInfo,"%s",dump.c_str()); } bool SS7M2PA::processMSG(unsigned char msgVersion, unsigned char msgClass, unsigned char msgType, const DataBlock& msg, int streamId) { if (msgClass != M2PA) { Debug(this,(msg.null() ? DebugInfo : DebugWarn), "Received non M2PA message class %d",msgClass); dumpMsg(msgVersion,msgClass,msgType,msg,streamId,false); return false; } if (m_dumpMsg) dumpMsg(msgVersion,msgClass,msgType,msg,streamId,false); Lock lock(m_mutex); if (!operational() && msgType == UserData) return false; if (!decodeSeq(msg,(u_int8_t)msgType)) return false; DataBlock data(msg); data.cut(-8); if (!data.length()) return true; if (msgType == LinkStatus) return m_sequenced ? processSLinkStatus(data,streamId) : processLinkStatus(data,streamId); #ifdef DEBUG if (streamId != 1) Debug(this,DebugNote,"Received data message on Link status stream"); #endif lock.drop(); data.cut(-1); // priority octet SS7MSU msu(data); return receivedMSU(msu); } bool SS7M2PA::nextBsn(u_int32_t bsn) const { u_int32_t n = (0x1000000 + m_seqNr - bsn) & 0xffffff; if (n > m_maxQueueSize) { Debug(this,DebugWarn,"Maximum number of unacknowledged messages reached!!!"); return false; } n = (0x1000000 + bsn - m_lastAck) & 0xffffff; return (n != 0) && (n <= m_maxQueueSize); } bool SS7M2PA::decodeSeq(const DataBlock& data,u_int8_t msgType) { if (data.length() < 8) return false; u_int32_t bsn = (data[1] << 16) | (data[2] << 8) | data[3]; u_int32_t fsn = (data[5] << 16) | (data[6] << 8) | data[7]; if (msgType == LinkStatus) { // Do not check sequence numbers if either end is OutOfService if (OutOfService == m_state) return true; if (data.length() >= 12) { u_int32_t status = (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11]; if (OutOfService == status) return true; } if (fsn != m_needToAck) { Debug(this,DebugWarn,"Received LinkStatus with wrong sequence %d, expected %d in state %s", fsn,m_needToAck,lookup(m_localStatus,s_state)); abortAlignment("Wrong Sequence number"); transmitLS(); return false; } if (bsn == m_lastAck) return true; // If we are here means that something went wrong abortAlignment("msgType == LinkStatus"); transmitLS(); return false; } // UserData bool ok = false; if (fsn == getNext(m_needToAck)) { m_needToAck = fsn; ok = true; if (m_confTimer.started()) { if (++m_confCounter >= m_maxUnack) { m_confTimer.stop(); sendAck(); } } else if (m_maxUnack) { m_confCounter = 0; m_confTimer.start(); } else sendAck(); } else if (fsn != m_needToAck) { abortAlignment("Received Out of sequence frame"); transmitLS(); return false; } while (nextBsn(bsn) && removeFrame(getNext(m_lastAck))) ; if (bsn != m_lastAck) { abortAlignment(String("Received unexpected bsn: ") << bsn); transmitLS(); return false; } m_lastSeqRx = (m_needToAck & 0x00ffffff) | 0x01000000; return ok; } void SS7M2PA::timerTick(const Time& when) { SS7Layer2::timerTick(when); Lock lock(m_mutex,SignallingEngine::maxLockWait()); if (!lock.locked()) return; if (m_confTimer.timeout(when.msec())) { sendAck(); // Acknowledge last received message before endpoint drops down the link m_confTimer.stop(); } if (m_ackTimer.timeout(when.msec())) { m_ackTimer.stop(); if (!transport() || transport()->reliable()) { lock.drop(); abortAlignment("Ack timer timeout"); } else retransData(); } if (m_waitOosTimer.timeout(when.msec())) { m_waitOosTimer.stop(); setLocalStatus(OutOfService); transmitLS(); } if (m_connFailTimer.timeout(when.msec())) { m_connFailTimer.stop(); if (m_connFailCounter >= m_connFailThreshold) { Debug(this,DebugMild, "Connection proving failed but transport was not restarted!"); restart(true); } m_connFailCounter = 0; } if (m_oosTimer.timeout(when.msec())) { m_oosTimer.stop(); if (m_transportState == Established) abortAlignment("Out of service timeout"); else m_oosTimer.start(); return; } if (m_t2.timeout(when.msec())) { abortAlignment("T2 timeout"); setLocalStatus(Alignment); transmitLS(); m_t2.start(); return; } if (m_t3.timeout(when.msec())) { m_t3.stop(); abortAlignment("T3 timeout"); return; } if (m_t4.started()) { if (m_t4.timeout(when.msec())) { m_t4.stop(); setLocalStatus(Ready); transmitLS(); m_t1.start(); return; } // Retransmit proving state if ((when & 0x3f) == 0) transmitLS(); } if (m_t1.timeout(when.msec())) { m_t1.stop(); abortAlignment("T1 timeout"); } } bool SS7M2PA::removeFrame(u_int32_t bsn) { Lock lock(m_mutex); for (ObjList* o = m_ackList.skipNull();o;o = o->skipNext()) { DataBlock* d = static_cast(o->get()); u_int32_t seq = (d->at(5) << 16) | (d->at(6) << 8) | d->at(7); if (bsn != seq) continue; m_lastAck = bsn; m_ackList.remove(d); m_ackTimer.stop(); return true; } Debug(this,DebugWarn,"Failed to remove frame %d! Frame is missing!",bsn); return false; } void SS7M2PA::setLocalStatus(unsigned int status) { if (status == m_localStatus) return; DDebug(this,DebugInfo,"Local status change %s -> %s [%p]", lookup(m_localStatus,s_state),lookup(status,s_state),this); if (status == Ready) m_ackList.clear(); m_localStatus = status; } void SS7M2PA::setRemoteStatus(unsigned int status) { if (status == m_remoteStatus) return; DDebug(this,DebugInfo,"Remote status change %s -> %s [%p]", lookup(m_remoteStatus,s_state),lookup(status,s_state),this); m_remoteStatus = status; } bool SS7M2PA::aligned() const { switch (m_localStatus) { case ProvingNormal: case ProvingEmergency: case Ready: switch (m_remoteStatus) { case ProvingNormal: case ProvingEmergency: case Ready: return true; } } return false; } bool SS7M2PA::operational() const { return m_localStatus == Ready && m_remoteStatus == Ready; } void SS7M2PA::sendAck() { DataBlock data; setHeader(data); if (m_dumpMsg) dumpMsg(1,M2PA,UserData,data,1,true); transmitMSG(1,M2PA,UserData,data,1); } unsigned int SS7M2PA::status() const { switch (m_localStatus) { case ProvingNormal: case ProvingEmergency: return SS7Layer2::OutOfAlignment; case Ready: switch (m_remoteStatus) { case Ready: return SS7Layer2::NormalAlignment; case ProcessorOutage: return SS7Layer2::ProcessorOutage; case Busy: return SS7Layer2::Busy; case OutOfService: return SS7Layer2::OutOfService; default: return SS7Layer2::OutOfAlignment; } } return SS7Layer2::OutOfService; } bool SS7M2PA::control(NamedList& params) { String* ret = params.getParam(YSTRING("completion")); const String* oper = params.getParam(YSTRING("operation")); const char* cmp = params.getValue(YSTRING("component")); int cmd = oper ? oper->toInteger(s_m2pa_dict_control,-1) : -1; if (ret) { if (oper && (cmd < 0)) return false; String part = params.getValue(YSTRING("partword")); if (cmp) { if (toString() != cmp) return false; for (const TokenDict* d = s_m2pa_dict_control; d->token; d++) Module::itemComplete(*ret,d->token,part); return true; } return Module::itemComplete(*ret,toString(),part); } if (!(cmp && toString() == cmp)) return false; return TelEngine::controlReturn(¶ms,(cmd >= 0) && control((M2PAOperations)cmd,¶ms)); } bool SS7M2PA::control(M2PAOperations oper, NamedList* params) { if (params) { m_autostart = params->getBoolValue(YSTRING("autostart"),m_autostart); m_autoEmergency = params->getBoolValue(YSTRING("autoemergency"),m_autoEmergency); m_maxUnack = params->getIntValue(YSTRING("max_unack"),m_maxUnack); if (m_maxUnack > 10) m_maxUnack = 10; } switch (oper) { case Pause: m_state = OutOfService; abortAlignment("Control request pause."); transmitLS(); return TelEngine::controlReturn(params,true); case Resume: if (aligned() || !m_autostart) return TelEngine::controlReturn(params,true); case Align: { m_state = getEmergency(params) ? ProvingEmergency : ProvingNormal; abortAlignment("Control request align."); return TelEngine::controlReturn(params,true); } case Status: return TelEngine::controlReturn(params,operational()); case TransRestart: return TelEngine::controlReturn(params,restart(true)); default: return TelEngine::controlReturn(params,false); } } void SS7M2PA::startAlignment(bool emergency) { setLocalStatus(OutOfService); transmitLS(); if (!m_sequenced) setLocalStatus(Alignment); m_oosTimer.start(); SS7Layer2::notify(); } void SS7M2PA::transmitLS(int streamId) { if (m_transportState != Established) return; if (m_state == OutOfService) m_localStatus = OutOfService; DataBlock data; setHeader(data); u_int8_t ms[4]; ms[2] = ms[1] = ms[0] = 0; ms[3] = m_localStatus; data.append(ms,4); if (m_dumpMsg) dumpMsg(1,M2PA, 2,data,streamId,true); transmitMSG(1,M2PA, 2, data,streamId); XDebug(this,DebugInfo,"Sending LinkStatus %s",lookup(m_localStatus,s_state)); } void SS7M2PA::setHeader(DataBlock& data) { u_int8_t head[8]; head[0] = head[4] = 0; head[1] = (m_needToAck >> 16) & 0xff; head[2] = (m_needToAck >> 8) & 0xff; head[3] = m_needToAck & 0xff ; head[5] = (m_seqNr >> 16) & 0xff; head[6] = (m_seqNr >> 8) & 0xff; head[7] = m_seqNr & 0xff ; data.append(head,8); } void SS7M2PA::abortAlignment(const char* info) { m_connFailCounter++; if (!m_connFailTimer.started()) m_connFailTimer.start(); else if (m_connFailCounter >= m_connFailThreshold) { restart(true); m_connFailTimer.stop(); } if (info) Debug(this,DebugNote,"Aborting alignment: %s",info); setLocalStatus(OutOfService); setRemoteStatus(OutOfService); m_needToAck = m_lastAck = m_seqNr = 0xffffff; m_confTimer.stop(); m_ackTimer.stop(); m_oosTimer.stop(); m_t2.stop(); m_t3.stop(); m_t4.stop(); m_t1.stop(); if (m_state == ProvingNormal || m_state == ProvingEmergency) { startAlignment(); if (m_sequenced) m_waitOosTimer.start(); } else SS7Layer2::notify(); } bool SS7M2PA::processLinkStatus(DataBlock& data,int streamId) { if (data.length() < 4) return false; u_int32_t status = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; if (m_remoteStatus == status && status != OutOfService) return true; XDebug(this,DebugAll,"Received link status: %s, local status : %s, requested status %s", lookup(status,s_state),lookup(m_localStatus,s_state),lookup(m_state,s_state)); switch (status) { case Alignment: m_oosTimer.stop(); if (m_t2.started()) { m_t2.stop(); setLocalStatus(m_state); m_t3.start(); transmitLS(); } else if (m_state == ProvingNormal || m_state == ProvingEmergency) transmitLS(); else return false; setRemoteStatus(status); break; case ProvingNormal: case ProvingEmergency: m_t2.stop(); if (m_localStatus != ProvingNormal && m_localStatus != ProvingEmergency && (m_localStatus == Alignment && m_t3.started())) return false; if (m_t3.started()) { m_t3.stop(); if (status == ProvingEmergency || m_state == ProvingEmergency) m_t4.fire(Time::msecNow() + (m_t4.interval() / 16)); else m_t4.start(); } else if (m_state == ProvingNormal || m_state == ProvingEmergency) { setLocalStatus(status); transmitLS(); if (status == ProvingEmergency || m_state == ProvingEmergency) m_t4.fire(Time::msecNow() + (m_t4.interval() / 16)); else m_t4.start(); } setRemoteStatus(status); break; case Ready: if (m_localStatus != Ready) { setLocalStatus(Ready); transmitLS(); } setRemoteStatus(status); m_lastSeqRx = -1; SS7Layer2::notify(); m_oosTimer.stop(); m_t2.stop(); m_t3.stop(); m_t4.stop(); m_t1.stop(); break; case ProcessorRecovered: transmitLS(); setRemoteStatus(status); break; case BusyEnded: setRemoteStatus(Ready); SS7Layer2::notify(); break; case ProcessorOutage: case Busy: setRemoteStatus(status); SS7Layer2::notify(); break; case OutOfService: m_oosTimer.stop(); if (m_localStatus == Ready) { abortAlignment("Received : LinkStatus Out of service, local status Ready"); SS7Layer2::notify(); } if ((m_state == ProvingNormal || m_state == ProvingEmergency)) { if (m_localStatus == Alignment) { transmitLS(); if (!m_t2.started()) m_t2.start(); } else if (m_localStatus == OutOfService) startAlignment(); else abortAlignment("Recv remote OOS"); } setRemoteStatus(status); break; default: Debug(this,DebugNote,"Received unknown link status message %d",status); return false; } return true; } bool SS7M2PA::processSLinkStatus(DataBlock& data,int streamId) { if (data.length() < 4) return false; u_int32_t status = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; if (m_remoteStatus == status && status != OutOfService) return true; if (m_waitOosTimer.started()) return true; Debug(this,DebugAll,"Received link status: %s, local status : %s, requested status %s", lookup(status,s_state),lookup(m_localStatus,s_state),lookup(m_state,s_state)); switch (status) { case Alignment: m_oosTimer.stop(); if (m_localStatus == Alignment && m_t2.started()) { m_t2.stop(); if (m_state == ProvingNormal || m_state == ProvingEmergency) { setLocalStatus(m_state); transmitLS(); m_t3.start(); } } else if (m_localStatus == OutOfService) { setLocalStatus(Alignment); transmitLS(); m_t3.start(); } else abortAlignment("Out of order alignment message"); setRemoteStatus(status); break; case ProvingNormal: case ProvingEmergency: m_t2.stop(); if (m_localStatus == Alignment && m_t3.started()) { m_t3.stop(); setLocalStatus(status); transmitLS(); if (status == ProvingEmergency || m_state == ProvingEmergency) m_t4.fire(Time::msecNow() + (m_t4.interval() / 16)); else m_t4.start(); } else if (m_localStatus == ProvingNormal || m_localStatus == ProvingEmergency) { m_t3.stop(); if (status == ProvingEmergency || m_state == ProvingEmergency) m_t4.fire(Time::msecNow() + (m_t4.interval() / 16)); else m_t4.start(); } else abortAlignment("Out of order proving message"); setRemoteStatus(status); break; case Ready: if (m_localStatus == ProvingNormal || m_localStatus == ProvingEmergency) { setLocalStatus(Ready); transmitLS(); } else if (m_localStatus != Ready) { abortAlignment("Out of order Ready message"); return true; } setRemoteStatus(status); m_lastSeqRx = -1; SS7Layer2::notify(); m_oosTimer.stop(); m_t2.stop(); m_t3.stop(); m_t4.stop(); m_t1.stop(); break; case ProcessorRecovered: transmitLS(); setRemoteStatus(status); break; case BusyEnded: setRemoteStatus(Ready); SS7Layer2::notify(); break; case ProcessorOutage: case Busy: setRemoteStatus(status); SS7Layer2::notify(); break; case OutOfService: if (!(m_state == ProvingNormal || m_state == ProvingEmergency)) { abortAlignment("Requested Pause"); setRemoteStatus(status); return true; } if (m_localStatus == OutOfService) { m_oosTimer.stop(); setLocalStatus(Alignment); transmitLS(); if (!m_t2.started()) m_t2.start(); } else if (m_localStatus == Alignment) transmitLS(); else { abortAlignment("Remote OOS"); m_waitOosTimer.fire(Time::msecNow() + (m_waitOosTimer.interval() / 2)); } setRemoteStatus(status); break; default: Debug(this,DebugNote,"Received unknown link status message %d",status); return false; } return true; } void SS7M2PA::recoverMSU(int sequence) { if (operational()) { Debug(this,DebugMild,"Recover MSU from sequence %d while link is operational",sequence); return; } Debug(this,DebugInfo,"Recovering MSUs from sequence %d",sequence); for (;;) { m_mutex.lock(); DataBlock* pkt = static_cast(m_ackList.remove(false)); m_mutex.unlock(); if (!pkt) break; unsigned char* head = pkt->data(0,8); if (head) { int seq = head[7] | ((int)head[6] << 8) | ((int)head[5] << 16); if (sequence < 0 || ((seq - sequence) & 0x00ffffff) < 0x007fffff) { sequence = -1; SS7MSU msu(head + 8,pkt->length() - 8); recoveredMSU(msu); } else Debug(this,DebugAll,"Not recovering MSU with seq=%d, requested %d", seq,sequence); } TelEngine::destruct(pkt); } } void SS7M2PA::retransData() { for (ObjList* o = m_ackList.skipNull();o;o = o->skipNext()) { DataBlock* msg = static_cast(o->get()); u_int8_t* head = (u_int8_t*)msg->data(); head[1] = (m_needToAck >> 16) & 0xff; head[2] = (m_needToAck >> 8) & 0xff; head[3] = m_needToAck & 0xff ; if (m_confTimer.started()) m_confTimer.stop(); if (!m_ackTimer.started()) m_ackTimer.start(); transmitMSG(1,M2PA, 1, *msg,1); } } bool SS7M2PA::transmitMSU(const SS7MSU& msu) { if (msu.length() < 3) { Debug(this,DebugWarn,"Asked to send too short MSU of length %u [%p]", msu.length(),this); return false; } // If we don't have an attached interface don't bother if (!transport()) return false; Lock lock(m_mutex); if (!operational()) return false; DataBlock packet; increment(m_seqNr); setHeader(packet); if (m_confTimer.started()) m_confTimer.stop(); static const DataBlock priority(0,1); packet += priority; packet += msu; m_ackList.append(new DataBlock(packet)); if (m_dumpMsg) dumpMsg(1,M2PA,1,packet,1,true); if (!m_ackTimer.started()) m_ackTimer.start(); return transmitMSG(1,M2PA,1,packet,1); } void SS7M2PA::notifyLayer(SignallingInterface::Notification event) { switch (event) { case SignallingInterface::LinkDown: m_transportState = Idle; m_connFailCounter = 0; abortAlignment("LinkDown"); m_connFailTimer.stop(); m_connFailCounter = 0; SS7Layer2::notify(); break; case SignallingInterface::LinkUp: { m_transportState = Established; Debug(this,DebugInfo,"Interface is up [%p]",this); String params = "rto_max"; NamedList result("sctp_params"); if (getSocketParams(params,result)) { int rtoMax = result.getIntValue(YSTRING("rto_max")); unsigned int maxRetrans = rtoMax + (int)m_confTimer.interval() + AVG_DELAY; if (maxRetrans > m_ackTimer.interval()) { Debug(this,DebugConf, "%s (%d) is greater than ack timer (%d)! Max RTO: %d, conf timer %d, avg delay: %d", "The maximum time interval to retransmit a packet", maxRetrans,(int)m_ackTimer.interval(), rtoMax,(int)m_confTimer.interval(),AVG_DELAY); } } else Debug(this,DebugNote,"Failed to obtain socket params"); if (m_autostart) startAlignment(); SS7Layer2::notify(); break; } case SignallingInterface::HardwareError: abortAlignment("HardwareError"); if (m_autostart && (m_transportState == Established)) startAlignment(); SS7Layer2::notify(); break; default: return; } } bool SS7M2UAClient::processMSG(unsigned char msgVersion, unsigned char msgClass, unsigned char msgType, const DataBlock& msg, int streamId) { u_int32_t iid = (u_int32_t)-1; if (MGMT == msgClass && getTag(msg,0x0001,iid)) { Lock mylock(this); for (ObjList* o = users().skipNull(); o; o = o->skipNext()) { AdaptUserPtr* p = static_cast(o->get()); RefPointer m2ua = static_cast(static_cast(*p)); if (!m2ua || (m2ua->iid() != (int32_t)iid)) continue; mylock.drop(); return m2ua->processMGMT(msgType,msg,streamId); } Debug(this,DebugStub,"Unhandled M2UA MGMT message type %u for IID=%u",msgType,iid); return false; } else if (MAUP != msgClass) return processCommonMSG(msgClass,msgType,msg,streamId); switch (msgType) { case 2: // Establish Request case 4: // Release Request case 7: // State Request case 10: // Data Retrieval Request Debug(this,DebugWarn,"Received M2UA SG request %u on ASP side!",msgType); return false; } getTag(msg,0x0001,iid); Lock mylock(this); for (ObjList* o = users().skipNull(); o; o = o->skipNext()) { AdaptUserPtr* p = static_cast(o->get()); RefPointer m2ua = static_cast(static_cast(*p)); if (!m2ua || (m2ua->iid() != (int32_t)iid)) continue; mylock.drop(); return m2ua->processMAUP(msgType,msg,streamId); } Debug(this,DebugStub,"Unhandled M2UA message type %u for IID=%d",msgType,(int32_t)iid); return false; } SS7M2UA::SS7M2UA(const NamedList& params) : SignallingComponent(params.safe("SS7M2UA"),¶ms,"ss7-m2ua"), m_retrieve(50), m_iid(params.getIntValue(YSTRING("iid"),-1)), m_linkState(LinkDown), m_rpo(false), m_longSeq(false) { DDebug(DebugInfo,"Creating SS7M2UA [%p]",this); m_retrieve.interval(params,"retrieve",5,200,true); m_longSeq = params.getBoolValue(YSTRING("longsequence")); m_lastSeqRx = -2; } bool SS7M2UA::initialize(const NamedList* config) { #ifdef DEBUG String tmp; if (config && debugAt(DebugAll)) config->dump(tmp,"\r\n ",'\'',true); Debug(this,DebugInfo,"SS7M2UA::initialize(%p) [%p]%s",config,this,tmp.c_str()); #endif m_autostart = !config || config->getBoolValue(YSTRING("autostart"),true); m_autoEmergency = !config || config->getBoolValue(YSTRING("autoemergency"),true); if (config && !adaptation()) { m_iid = config->getIntValue(YSTRING("iid"),m_iid); NamedList params(""); if (resolveConfig(YSTRING("client"),params,config) || resolveConfig(YSTRING("basename"),params,config)) { DDebug(this,DebugInfo,"Creating adaptation '%s' for SS7 M2UA [%p]", params.c_str(),this); params.addParam("basename",params); SS7M2UAClient* client = YOBJECT(SS7M2UAClient,engine()->build("SS7M2UAClient",params,false)); if (!client) return false; adaptation(client); client->initialize(¶ms); TelEngine::destruct(client); } } return transport() && control(Resume,const_cast(config)); } bool SS7M2UA::control(Operation oper, NamedList* params) { if (params) { m_autostart = params->getBoolValue(YSTRING("autostart"),m_autostart); m_autoEmergency = params->getBoolValue(YSTRING("autoemergency"),m_autoEmergency); m_longSeq = params->getBoolValue(YSTRING("longsequence"),m_longSeq); } switch (oper) { case Pause: if (aspActive()) { DataBlock buf; if (m_iid >= 0) SIGAdaptation::addTag(buf,0x0001,(u_int32_t)m_iid); // Release Request if (!adaptation()->transmitMSG(SIGTRAN::MAUP,4,buf,getStreamId())) return TelEngine::controlReturn(params,false); getSequence(); } m_linkState = LinkDown; if (!m_retrieve.started()) SS7Layer2::notify(); return TelEngine::controlReturn(params,true); case Resume: if (operational()) return TelEngine::controlReturn(params,true); if (!m_autostart) return TelEngine::controlReturn(params,activate()); if (m_retrieve.started()) { if (LinkDown == m_linkState) m_linkState = getEmergency(params,false) ? LinkReqEmg : LinkReq; return TelEngine::controlReturn(params,activate()); } // fall through case Align: if (aspActive()) { if (operational()) { m_linkState = LinkDown; SS7Layer2::notify(); } bool emg = (LinkUpEmg == m_linkState) || (LinkReqEmg == m_linkState); emg = getEmergency(params,emg); m_linkState = emg ? LinkReqEmg : LinkReq; DataBlock buf; if (m_iid >= 0) SIGAdaptation::addTag(buf,0x0001,(u_int32_t)m_iid); SIGAdaptation::addTag(buf,0x0302,(emg ? 2 : 3)); // State Request if (!adaptation()->transmitMSG(SIGTRAN::MAUP,7,buf,getStreamId())) return TelEngine::controlReturn(params,false); buf.clear(); if (m_iid >= 0) SIGAdaptation::addTag(buf,0x0001,(u_int32_t)m_iid); // Establish Request return TelEngine::controlReturn(params, adaptation()->transmitMSG(SIGTRAN::MAUP,2,buf,getStreamId())); } return TelEngine::controlReturn(params,activate()); case Status: return TelEngine::controlReturn(params,operational()); default: return TelEngine::controlReturn(params,false); } } unsigned int SS7M2UA::status() const { switch (m_linkState) { case LinkDown: return SS7Layer2::OutOfService; case LinkUp: return m_rpo ? SS7Layer2::ProcessorOutage : SS7Layer2::NormalAlignment; case LinkUpEmg: return m_rpo ? SS7Layer2::ProcessorOutage : SS7Layer2::EmergencyAlignment; } return SS7Layer2::OutOfAlignment; } bool SS7M2UA::transmitMSU(const SS7MSU& msu) { if (msu.length() < 3) { Debug(this,DebugWarn,"Asked to send too short MSU of length %u [%p]", msu.length(),this); return false; } Lock mylock(adaptation()); // If we don't have an attached interface don't bother if (!transport()) return false; DataBlock buf; if (m_iid >= 0) SIGAdaptation::addTag(buf,0x0001,(u_int32_t)m_iid); SIGAdaptation::addTag(buf,0x0300,msu); // Data return adaptation()->transmitMSG(SIGTRAN::MAUP,1,buf,getStreamId()); } void SS7M2UA::recoverMSU(int sequence) { Lock mylock(adaptation()); if (sequence >= 0 && aspUp() && transport()) { Debug(this,DebugInfo,"Retrieving MSUs from sequence %d from M2UA SG",sequence); DataBlock buf; if (m_iid >= 0) SIGAdaptation::addTag(buf,0x0001,(u_int32_t)m_iid); // Retrieve MSGS action SIGAdaptation::addTag(buf,0x0306,(u_int32_t)0); SIGAdaptation::addTag(buf,0x0307,(u_int32_t)sequence); // Data Retrieval Request adaptation()->transmitMSG(SIGTRAN::MAUP,10,buf,getStreamId()); } } int SS7M2UA::getSequence() { if (m_lastSeqRx == -1) { m_lastSeqRx = -2; Lock mylock(adaptation()); if (aspUp() && transport()) { Debug(this,DebugInfo,"Requesting sequence number from M2UA SG"); DataBlock buf; if (m_iid >= 0) SIGAdaptation::addTag(buf,0x0001,(u_int32_t)m_iid); // Retrieve BSN action SIGAdaptation::addTag(buf,0x0306,(u_int32_t)1); // Data Retrieval Request if (adaptation()->transmitMSG(SIGTRAN::MAUP,10,buf,getStreamId())) m_retrieve.start(); } } return m_lastSeqRx; } void SS7M2UA::timerTick(const Time& when) { SS7Layer2::timerTick(when); if (m_retrieve.timeout(when.msec())) { m_retrieve.stop(); if (m_lastSeqRx == -2) { Debug(this,DebugWarn,"Sequence retrieval from M2UA SG timed out"); SS7Layer2::notify(); } if (m_linkState != LinkDown) control(Resume); } } bool SS7M2UA::processMGMT(unsigned char msgType, const DataBlock& msg, int streamId) { const char* err = "Unhandled"; switch (msgType) { case SIGTRAN::MgmtERR: { u_int32_t errCode = 0; if (SIGAdaptation::getTag(msg,0x000c,errCode)) { switch (errCode) { case 2: Debug(this,DebugWarn,"M2UA SG reported invalid IID=%d",m_iid); m_linkState = LinkDown; return true; default: Debug(this,DebugWarn,"M2UA SG reported error %u: %s",errCode,lookup(errCode,s_uaErrors,"Unknown")); return true; } } } err = "Error"; break; } Debug(this,DebugStub,"%s M2UA MGMT message type %u",err,msgType); return false; } bool SS7M2UA::processMAUP(unsigned char msgType, const DataBlock& msg, int streamId) { const char* err = "Unhandled"; switch (msgType) { case 1: // Data { SS7MSU data; if (!SIGAdaptation::getTag(msg,0x0300,data)) { err = "Missing data in"; break; } u_int32_t corrId; if (SIGAdaptation::getTag(msg,0x0013,corrId)) { // Correlation ID present, send Data Ack DataBlock buf; SIGAdaptation::addTag(buf,0x0013,corrId); adaptation()->transmitMSG(SIGTRAN::MAUP,15,buf,streamId); } return receivedMSU(data); } break; case 3: // Establish Confirm m_lastSeqRx = -1; m_linkState = LinkUp; m_congestion = 0; m_rpo = false; SS7Layer2::notify(); return true; case 5: // Release Confirm case 6: // Release Indication activeChange(false); return true; case 8: // State Confirm err = "Ignoring"; break; case 9: // State Indication { u_int32_t evt = 0; if (!SIGAdaptation::getTag(msg,0x0303,evt)) { err = "Missing state event"; break; } bool oper = operational(); switch (evt) { case 1: Debug(this,DebugInfo,"Remote entered Processor Outage"); m_rpo = true; break; case 2: Debug(this,DebugInfo,"Remote exited Processor Outage"); m_rpo = false; break; } if (operational() != oper) SS7Layer2::notify(); } return true; case 11: // Data Retrieval Confirm { u_int32_t res = 0; if (!SIGAdaptation::getTag(msg,0x0308,res)) { err = "Missing retrieval result"; break; } if (res) { err = "Retrieval failed"; break; } if (SIGAdaptation::getTag(msg,0x0306,res) && (res == 1)) { // Action was BSN retrieval res = (u_int32_t)-1; if (!SIGAdaptation::getTag(msg,0x0307,res)) { err = "Missing BSN field in retrieval"; m_lastSeqRx = -3; postRetrieve(); break; } Debug(this,DebugInfo,"Recovered sequence number %u",res); if (m_longSeq || res & 0xffffff80) res = (res & 0x00ffffff) | 0x01000000; m_lastSeqRx = res; postRetrieve(); return true; } } break; case 12: // Data Retrieval Indication case 13: // Data Retrieval Complete Indication { SS7MSU data; if (!SIGAdaptation::getTag(msg,0x0300,data)) { if (msgType == 13) return true; err = "Missing data in"; break; } return recoveredMSU(data); } break; case 14: // Congestion Indication { u_int32_t cong = 0; if (!SIGAdaptation::getTag(msg,0x0304,cong)) { err = "Missing congestion state"; break; } u_int32_t disc = 0; SIGAdaptation::getTag(msg,0x0305,disc); int level = disc ? DebugWarn : (cong ? DebugMild : DebugNote); Debug(this,level,"Congestion level %u, discard level %u",cong,disc); m_congestion = cong; } return true; } Debug(this,DebugStub,"%s M2UA MAUP message type %u",err,msgType); return false; } void SS7M2UA::postRetrieve() { if (!m_retrieve.started()) return; m_retrieve.stop(); SS7Layer2::notify(); m_retrieve.fire(Time::msecNow()+100); } void SS7M2UA::activeChange(bool active) { if (!active) { getSequence(); m_congestion = 0; m_rpo = false; switch (m_linkState) { case LinkUpEmg: m_linkState = LinkReqEmg; if (!m_retrieve.started()) SS7Layer2::notify(); break; case LinkUp: m_linkState = LinkReq; if (!m_retrieve.started()) SS7Layer2::notify(); break; case LinkReqEmg: case LinkReq: break; default: return; } } control(Resume); } bool SS7M2UA::operational() const { return (m_linkState >= LinkUp) && !m_rpo; } /** * ISDNIUAClient */ bool ISDNIUAClient::processMSG(unsigned char msgVersion, unsigned char msgClass, unsigned char msgType, const DataBlock& msg, int streamId) { u_int32_t iid = (u_int32_t)-1; if (MGMT == msgClass && getTag(msg,0x0001,iid)) { Lock mylock(this); for (ObjList* o = users().skipNull(); o; o = o->skipNext()) { AdaptUserPtr* p = static_cast(o->get()); RefPointer iua = static_cast(static_cast(*p)); if (!iua || (iua->iid() != (int32_t)iid)) continue; mylock.drop(); return iua->processMGMT(msgType,msg,streamId); } Debug(this,DebugStub,"Unhandled IUA MGMT message type %u for IID=%u",msgType,iid); return false; } else if (QPTM != msgClass) return processCommonMSG(msgClass,msgType,msg,streamId); switch (msgType) { case 1: // Data Request Message case 3: // Unit Data Request Message case 5: // Establish Request case 8: // Release Request Debug(this,DebugWarn,"Received IUA SG request %u on ASP side!",msgType); return false; } getTag(msg,0x0001,iid); Lock mylock(this); for (ObjList* o = users().skipNull(); o; o = o->skipNext()) { AdaptUserPtr* p = static_cast(o->get()); RefPointer iua = static_cast(static_cast(*p)); if (!iua || (iua->iid() != (int32_t)iid)) continue; mylock.drop(); return iua->processQPTM(msgType,msg,streamId); } Debug(this,DebugStub,"Unhandled IUA message type %u for IID=%d",msgType,(int32_t)iid); return false; } ISDNIUA::ISDNIUA(const NamedList& params, const char *name, u_int8_t tei) : SignallingComponent(params.safe(name ? name : "ISDNIUA"),¶ms,"isdn-iua"), ISDNLayer2(params,name,tei), m_iid(params.getIntValue(YSTRING("iid"),-1)) { DDebug(DebugInfo,"Creating ISDNIUA [%p]",this); } ISDNIUA::~ISDNIUA() { Lock lock(l2Mutex()); cleanup(); ISDNLayer2::attach((ISDNLayer3*)0); } bool ISDNIUA::multipleFrame(u_int8_t tei, bool establish, bool force) { Lock lock(l2Mutex()); if (!transport()) return false; if ((localTei() != tei) || (state() == WaitEstablish) || (state() == WaitRelease)) return false; if (!force && ((establish && (state() == Established)) || (!establish && (state() == Released)))) return false; XDebug(this,DebugAll,"Process '%s' request, TEI=%u", establish ? "ESTABLISH" : "RELEASE",tei); DataBlock buf; if (m_iid >= 0) SIGAdaptation::addTag(buf,0x0001,(u_int32_t)m_iid); u_int32_t dlci = 0x10000 | ((unsigned int)tei << 17); SIGAdaptation::addTag(buf,0x0005,dlci); if (establish) changeState(WaitEstablish,"multiple frame"); else { SIGAdaptation::addTag(buf,0x000f,(u_int32_t)(force ? 2 : 0)); changeState(WaitRelease,"multiple frame"); multipleFrameReleased(tei,true,false); } // Establish Request or Release Request return adaptation()->transmitMSG(SIGTRAN::QPTM,(establish ? 5 : 8),buf,getStreamId()); } bool ISDNIUA::sendData(const DataBlock& data, u_int8_t tei, bool ack) { if (data.null()) return false; Lock lock(l2Mutex()); if (!transport()) return false; DataBlock buf; if (m_iid >= 0) SIGAdaptation::addTag(buf,0x0001,(u_int32_t)m_iid); u_int32_t dlci = 0x10000 | ((unsigned int)tei << 17); SIGAdaptation::addTag(buf,0x0005,dlci); SIGAdaptation::addTag(buf,0x000e,data); // Data Request Message or Unit Data Request Message return adaptation()->transmitMSG(SIGTRAN::QPTM,(ack ? 1 : 3),buf,getStreamId()); } void ISDNIUA::cleanup() { Lock lock(l2Mutex()); DDebug(this,DebugAll,"Cleanup in state '%s'",stateName(state())); if (state() == Established) multipleFrame(localTei(),false,true); changeState(Released,"cleanup"); } bool ISDNIUA::processMGMT(unsigned char msgType, const DataBlock& msg, int streamId) { const char* err = "Unhandled"; switch (msgType) { case SIGTRAN::MgmtERR: { u_int32_t errCode = 0; if (SIGAdaptation::getTag(msg,0x000c,errCode)) { switch (errCode) { case 2: Debug(this,DebugWarn,"IUA SG reported invalid IID=%d",m_iid); changeState(Released,"invalid IID"); multipleFrameReleased(localTei(),false,true); return true; case 10: Debug(this,DebugWarn,"IUA SG reported unassigned TEI"); changeState(Released,"unassigned TEI"); multipleFrameReleased(localTei(),false,true); return true; case 12: Debug(this,DebugWarn,"IUA SG reported unrecognized SAPI"); changeState(Released,"unrecognized SAPI"); multipleFrameReleased(localTei(),false,true); return true; default: Debug(this,DebugWarn,"IUA SG reported error %u: %s",errCode,lookup(errCode,s_uaErrors,"Unknown")); return true; } } } err = "Error"; break; case 2: // TEI Status Request err = "Wrong direction TEI Status Request"; break; case 3: // TEI Status Confirm case 4: // TEI Status Indication { u_int32_t status = 0; if (!SIGAdaptation::getTag(msg,0x0010,status)) { err = "Missing TEI status in"; break; } u_int32_t dlci = 0; if (!SIGAdaptation::getTag(msg,0x0005,dlci)) { err = "Missing DLCI in"; break; } u_int8_t tei = (dlci >> 17) & 0x7e; Debug(this,DebugNote,"%sTEI %u Status is %s", (localTei() == tei ? "Our " : ""),tei, (status ? "unassigned" : "assigned")); if (status && (localTei() == tei)) { changeState(Released,"unassigned TEI"); multipleFrameReleased(localTei(),false,true); } return true; } case 5: // TEI Query Request err = "Wrong direction TEI Status Query"; break; } Debug(this,DebugStub,"%s IUA MGMT message type %u",err,msgType); return false; } bool ISDNIUA::processQPTM(unsigned char msgType, const DataBlock& msg, int streamId) { const char* err = "Unhandled"; switch (msgType) { case 2: // Data Request Message case 4: // Unit Data Request Message { u_int32_t dlci = 0; if (!SIGAdaptation::getTag(msg,0x0005,dlci)) { err = "Missing DLCI in"; break; } DataBlock data; if (!SIGAdaptation::getTag(msg,0x000e,data)) { err = "Missing data in"; break; } receiveData(data,(dlci >> 17) & 0x7e); return true; } break; case 6: // Establish Confirm case 7: // Establish Indication changeState(Established); multipleFrameEstablished(localTei(),(6 == msgType),false); return true; case 9: // Release Confirm changeState(Released,"remote confirm"); multipleFrameReleased(localTei(),true,false); return true; case 10: // Release Indication { u_int32_t reason = 0; if (SIGAdaptation::getTag(msg,0x000f,reason)) Debug(this,DebugMild,"IUA SG released interface, reason %d",reason); else Debug(this,DebugMild,"IUA SG released interface, no reason"); } changeState(Released,"remote indication"); multipleFrameReleased(localTei(),false,true); return true; } Debug(this,DebugStub,"%s IUA QPTM message type %u",err,msgType); return false; } void ISDNIUA::activeChange(bool active) { if (active) { if (m_autostart) multipleFrame(localTei(),true,false); } else { changeState(Released,"remote inactive"); multipleFrameReleased(localTei(),false,true); } } bool ISDNIUA::initialize(const NamedList* config) { #ifdef DEBUG String tmp; if (config && debugAt(DebugAll)) config->dump(tmp,"\r\n ",'\'',true); Debug(this,DebugInfo,"ISDNIUA::initialize(%p) [%p]%s",config,this,tmp.c_str()); #endif m_autostart = !config || config->getBoolValue(YSTRING("autostart"),true); if (config && !adaptation()) { m_iid = config->getIntValue(YSTRING("iid"),m_iid); NamedList params(""); if (resolveConfig(YSTRING("client"),params,config) || resolveConfig(YSTRING("basename"),params,config)) { DDebug(this,DebugInfo,"Creating adaptation '%s' for ISDN UA [%p]", params.c_str(),this); params.addParam("basename",params); ISDNIUAClient* client = YOBJECT(ISDNIUAClient,engine()->build("ISDNIUAClient",params,false)); if (!client) return false; adaptation(client); client->initialize(¶ms); TelEngine::destruct(client); } } if (!transport()) return false; return (m_autostart && aspActive()) ? multipleFrame(localTei(),true,false) : activate(); } /* vi: set ts=8 sw=4 sts=4 noet: */