/*
* TeleStax, Open Source Cloud Communications Copyright 2012.
* and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.mobicents.protocols.ss7.sccp.impl;
import io.netty.util.concurrent.DefaultThreadFactory;
import javolution.text.TextBuilder;
import javolution.util.FastMap;
import javolution.xml.XMLBinding;
import javolution.xml.XMLObjectReader;
import javolution.xml.XMLObjectWriter;
import javolution.xml.stream.XMLStreamException;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.mobicents.protocols.ss7.indicator.RoutingIndicator;
import org.mobicents.protocols.ss7.mtp.Mtp3;
import org.mobicents.protocols.ss7.mtp.Mtp3EndCongestionPrimitive;
import org.mobicents.protocols.ss7.mtp.Mtp3PausePrimitive;
import org.mobicents.protocols.ss7.mtp.Mtp3ResumePrimitive;
import org.mobicents.protocols.ss7.mtp.Mtp3StatusCause;
import org.mobicents.protocols.ss7.mtp.Mtp3StatusPrimitive;
import org.mobicents.protocols.ss7.mtp.Mtp3TransferPrimitive;
import org.mobicents.protocols.ss7.mtp.Mtp3UserPart;
import org.mobicents.protocols.ss7.mtp.Mtp3UserPartListener;
import org.mobicents.protocols.ss7.sccp.LongMessageRule;
import org.mobicents.protocols.ss7.sccp.LongMessageRuleType;
import org.mobicents.protocols.ss7.sccp.Mtp3ServiceAccessPoint;
import org.mobicents.protocols.ss7.sccp.NetworkIdState;
import org.mobicents.protocols.ss7.sccp.RemoteSignalingPointCode;
import org.mobicents.protocols.ss7.sccp.Router;
import org.mobicents.protocols.ss7.sccp.Rule;
import org.mobicents.protocols.ss7.sccp.SccpCongestionControlAlgo;
import org.mobicents.protocols.ss7.sccp.SccpManagementEventListener;
import org.mobicents.protocols.ss7.sccp.SccpProtocolVersion;
import org.mobicents.protocols.ss7.sccp.SccpProvider;
import org.mobicents.protocols.ss7.sccp.SccpResource;
import org.mobicents.protocols.ss7.sccp.SccpStack;
import org.mobicents.protocols.ss7.sccp.impl.congestion.SccpCongestionControl;
import org.mobicents.protocols.ss7.sccp.impl.message.MessageFactoryImpl;
import org.mobicents.protocols.ss7.sccp.impl.message.SccpAddressedMessageImpl;
import org.mobicents.protocols.ss7.sccp.impl.message.SccpDataMessageImpl;
import org.mobicents.protocols.ss7.sccp.impl.message.SccpMessageImpl;
import org.mobicents.protocols.ss7.sccp.impl.message.SccpSegmentableMessageImpl;
import org.mobicents.protocols.ss7.sccp.impl.parameter.SccpAddressImpl;
import org.mobicents.protocols.ss7.sccp.impl.parameter.SegmentationImpl;
import org.mobicents.protocols.ss7.sccp.impl.router.RouterImpl;
import org.mobicents.protocols.ss7.sccp.parameter.GlobalTitle;
import org.mobicents.protocols.ss7.sccp.parameter.ReturnCauseValue;
import org.mobicents.protocols.ss7.sccp.parameter.SccpAddress;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.mobicents.protocols.ss7.sccp.impl.message.MessageUtil.calculateLudtFieldsLengthWithoutData;
import static org.mobicents.protocols.ss7.sccp.impl.message.MessageUtil.calculateUdtFieldsLengthWithoutData;
import static org.mobicents.protocols.ss7.sccp.impl.message.MessageUtil.calculateXudtFieldsLengthWithoutData;
import static org.mobicents.protocols.ss7.sccp.impl.message.MessageUtil.calculateXudtFieldsLengthWithoutData2;
/**
*
* @author amit bhayani
* @author baranowb
* @author sergey vetyutnev
*
*/
public class SccpStackImpl implements SccpStack, Mtp3UserPartListener {
protected final Logger logger;
protected static final String SCCP_MANAGEMENT_PERSIST_DIR_KEY = "sccpmanagement.persist.dir";
protected static final String USER_DIR_KEY = "user.dir";
protected static final String PERSIST_FILE_NAME = "management2.xml";
private static final String TAB_INDENT = "\t";
private static final String CLASS_ATTRIBUTE = "type";
private static final String Z_MARGIN_UDT_MSG = "zmarginxudtmessage";
private static final String REASSEMBLY_TIMER_DELAY = "reassemblytimerdelay";
private static final String MAX_DATA_MSG = "maxdatamessage";
private static final String REMOVE_SPC = "removespc";
private static final String RESERVED_FOR_NATIONAL_USE_VALUE_ADDRESS_INDICATOR = "reservedfornationalusevalue_addressindicator";
private static final String SCCP_PROTOCOL_VERSION = "sccpProtocolVersion";
private static final String PREVIEW_MODE = "previewMode";
private static final String SST_TIMER_DURATION_MIN = "ssttimerduration_min";
private static final String SST_TIMER_DURATION_MAX = "ssttimerduration_max";
private static final String SST_TIMER_DURATION_INCREASE_FACTOR = "ssttimerduration_increasefactor";
private static final String CONG_CONTROL_TIMER_A = "congControl_TIMER_A";
private static final String CONG_CONTROL_TIMER_D = "congControl_TIMER_D";
private static final String CONG_CONTROL_ALGO = "congControl_Algo";
private static final String CONG_CONTROL_BLOCKING_OUTGOUNG_SCCP_MESSAGES = "congControl_blockingOutgoungSccpMessages";
/**
* Interval in milliseconds in which new coming for an affected PC MTP-STATUS messages will be logged
*/
private static final int STATUS_MSG_LOGGING_INTERVAL_MILLISEC_CONG = 10000;
private static final int STATUS_MSG_LOGGING_INTERVAL_MILLISEC_UNAVAIL = 100;
private static final XMLBinding binding = new XMLBinding();
// If the XUDT message data length greater this value, segmentation is
// needed
protected int zMarginXudtMessage = 240;
// sccp segmented message reassembling timeout
protected int reassemblyTimerDelay = 15000;
// Max available Sccp message data for all messages
protected int maxDataMessage = 2560;
// remove PC from calledPartyAddress when sending to MTP3
private boolean removeSpc = true;
// min (starting) SST sending interval (millisec)
protected int sstTimerDuration_Min = 10000;
// max (after increasing) SST sending interval (millisec)
protected int sstTimerDuration_Max = 600000;
// multiplicator of SST sending interval (next interval will be greater the
// current by sstTimerDuration_IncreaseFactor)
// TODO: make it configurable
protected double sstTimerDuration_IncreaseFactor = 1.5;
// Which SCCP protocol version stack processes (ITU / ANSI)
private SccpProtocolVersion sccpProtocolVersion = SccpProtocolVersion.ITU;
// SCCP congestion control - the count of levels for restriction level - RLM
protected int congControl_N = 8;
// SCCP congestion control - the count of sublevels for restriction level - RSLM
protected int congControl_M = 4;
// Timer Ta value - started at next MTP-STATUS(cong) primitive coming; during this timer no more MTP-STATUS(cong) are
// accepted
protected int congControl_TIMER_A = 400; // 200
// Timer Td value - started after last MTP-STATUS(cong) primitive coming; after end of this timer (without new coming
// MTP-STATUS(cong)) RSLM will be reduced
protected int congControl_TIMER_D = 2000; // 2000
// sccp congestion control
// international: international algorithm - only one level is provided by MTP3 level (in MTP-STATUS primitive). Each
// MTP-STATUS increases N / M levels
// levelDepended: MTP3 level (MTP-STATUS primitive) provides 3 levels of a congestion (1-3) and SCCP congestion will
// increase to the
// next level after MTP-STATUS next level increase (MTP-STATUS 1 to N up to 3, MTP-STATUS 2 to N up to 5, MTP-STATUS 3
// to N up to 7)
protected SccpCongestionControlAlgo congControl_Algo = SccpCongestionControlAlgo.international;
// if true outgoing SCCP messages will be blocked (depending on message type, UDP messages from level N=6)
protected boolean congControl_blockingOutgoungSccpMessages = false;
// The count of threads that will be used for message delivering to
// SccpListener's for SCCP user -> SCCP -> SCCP user transit (without MTP part)
protected int deliveryTransferMessageThreadCount = 4;
private boolean previewMode = false;
protected volatile State state = State.IDLE;
// provider ref, this can be real provider or pipe, for tests.
protected SccpProviderImpl sccpProvider;
protected RouterImpl router;
protected SccpResourceImpl sccpResource;
protected MessageFactoryImpl messageFactory;
protected SccpManagement sccpManagement;
protected SccpRoutingControl sccpRoutingControl;
protected SccpCongestionControl sccpCongestionControl;
protected FastMap<Integer, Mtp3UserPart> mtp3UserParts = new FastMap<Integer, Mtp3UserPart>();
protected ScheduledExecutorService timerExecutors;
protected FastMap<MessageReassemblyProcess, SccpSegmentableMessageImpl> reassemplyCache = new FastMap<MessageReassemblyProcess, SccpSegmentableMessageImpl>();
// executors for delivering messages SCCP user -> SCCP -> SCCP user (for messages that are not from or to MTP part)
protected ExecutorService[] msgDeliveryExecutors;
protected int slsFilter = 0x0f;
protected int[] slsTable = null;
// protected int localSpc;
// protected int ni = 2;
protected final String name;
protected final TextBuilder persistFile = TextBuilder.newInstance();
protected String persistDir = null;
private boolean rspProhibitedByDefault;
private volatile int segmentationLocalRef = 0;
private volatile int slsCounter = 0;
private volatile int selectorCounter = 0;
private FastMap<Integer, Date> lastCongNotice = new FastMap<Integer, Date>();
private FastMap<Integer, Date> lastUserPartUnavailNotice = new FastMap<Integer, Date>();
public SccpStackImpl(String name) {
binding.setClassAttribute(CLASS_ATTRIBUTE);
this.name = name;
this.logger = Logger.getLogger(SccpStackImpl.class.getCanonicalName() + "-" + this.name);
this.messageFactory = new MessageFactoryImpl(this);
this.sccpProvider = new SccpProviderImpl(this);
this.state = State.CONFIGURED;
}
public String getName() {
return this.name;
}
public String getPersistDir() {
return persistDir;
}
public void setPersistDir(String persistDir) {
this.persistDir = persistDir;
}
public void setRspProhibitedByDefault(boolean rspProhibitedByDefault) {
this.rspProhibitedByDefault = rspProhibitedByDefault;
}
public boolean isRspProhibitedByDefault() {
return rspProhibitedByDefault;
}
public SccpProvider getSccpProvider() {
return sccpProvider;
}
public Map<Integer, Mtp3UserPart> getMtp3UserParts() {
return mtp3UserParts;
}
public void setMtp3UserParts(Map<Integer, Mtp3UserPart> mtp3UserPartsTemp) {
if (mtp3UserPartsTemp != null) {
synchronized (this) {
FastMap<Integer, Mtp3UserPart> newMtp3UserPart = new FastMap<Integer, Mtp3UserPart>();
newMtp3UserPart.putAll(mtp3UserPartsTemp);
this.mtp3UserParts = newMtp3UserPart;
}
}
}
public Mtp3UserPart getMtp3UserPart(int id) {
return mtp3UserParts.get(id);
}
public void setMtp3UserPart(int id, Mtp3UserPart mtp3UserPart) {
if (mtp3UserPart == null) {
this.removeMtp3UserPart(id);
} else {
synchronized (this) {
FastMap<Integer, Mtp3UserPart> newMtp3UserPart = new FastMap<Integer, Mtp3UserPart>();
newMtp3UserPart.putAll(this.mtp3UserParts);
newMtp3UserPart.put(id, mtp3UserPart);
this.mtp3UserParts = newMtp3UserPart;
}
}
}
public void removeMtp3UserPart(int id) {
synchronized (this) {
FastMap<Integer, Mtp3UserPart> newMtp3UserPart = new FastMap<Integer, Mtp3UserPart>();
newMtp3UserPart.putAll(this.mtp3UserParts);
newMtp3UserPart.remove(id);
this.mtp3UserParts = newMtp3UserPart;
}
}
public void setRemoveSpc(boolean removeSpc) throws Exception {
if (!this.isStarted())
throw new Exception("RemoveSpc parameter can be updated only when SCCP stack is running");
this.removeSpc = removeSpc;
this.store();
}
public void setSccpProtocolVersion(SccpProtocolVersion sccpProtocolVersion) throws Exception {
if (!this.isStarted())
throw new Exception("SccpProtocolVersion parameter can be updated only when SCCP stack is running");
if (sccpProtocolVersion != null)
this.sccpProtocolVersion = sccpProtocolVersion;
this.store();
}
public void setPreviewMode(boolean previewMode) throws Exception {
if (!this.isStarted())
throw new Exception("PreviewMode parameter can be updated only when SCCP stack is running");
this.previewMode = previewMode;
this.store();
}
public int getDeliveryMessageThreadCount() {
return this.deliveryTransferMessageThreadCount;
}
public void setDeliveryMessageThreadCount(int deliveryMessageThreadCount) throws Exception {
if (this.isStarted())
throw new Exception("DeliveryMessageThreadCount parameter can be updated only when SCCP stack is NOT running");
if (deliveryMessageThreadCount > 0 && deliveryMessageThreadCount <= 100)
this.deliveryTransferMessageThreadCount = deliveryMessageThreadCount;
}
public void setSstTimerDuration_Min(int sstTimerDuration_Min) throws Exception {
if (!this.isStarted())
throw new Exception("SstTimerDuration_Min parameter can be updated only when SCCP stack is running");
// 5-10 seconds
if (sstTimerDuration_Min < 5000)
sstTimerDuration_Min = 5000;
if (sstTimerDuration_Min > 10000)
sstTimerDuration_Min = 10000;
this.sstTimerDuration_Min = sstTimerDuration_Min;
this.store();
}
public void setSstTimerDuration_Max(int sstTimerDuration_Max) throws Exception {
if (!this.isStarted())
throw new Exception("SstTimerDuration_Max parameter can be updated only when SCCP stack is running");
// 10-20 minutes
if (sstTimerDuration_Max < 600000)
sstTimerDuration_Max = 600000;
if (sstTimerDuration_Max > 1200000)
sstTimerDuration_Max = 1200000;
this.sstTimerDuration_Max = sstTimerDuration_Max;
this.store();
}
public void setSstTimerDuration_IncreaseFactor(double sstTimerDuration_IncreaseFactor) throws Exception {
if (!this.isStarted())
throw new Exception("SstTimerDuration_IncreaseFactor parameter can be updated only when SCCP stack is running");
// acceptable factor from 1 to 4
if (sstTimerDuration_IncreaseFactor < 1)
sstTimerDuration_IncreaseFactor = 1;
if (sstTimerDuration_IncreaseFactor > 4)
sstTimerDuration_IncreaseFactor = 4;
this.sstTimerDuration_IncreaseFactor = sstTimerDuration_IncreaseFactor;
this.store();
}
public int getCongControlTIMER_A() {
return congControl_TIMER_A;
}
public void setCongControlTIMER_A(int value) throws Exception {
if (!this.isStarted())
throw new Exception("CongControlTIMER_A parameter can be updated only when SCCP stack is running");
if (value < 60)
value = 60;
if (value > 1000)
value = 1000;
congControl_TIMER_A = value;
this.store();
}
public int getCongControlTIMER_D() {
return congControl_TIMER_D;
}
public void setCongControlTIMER_D(int value) throws Exception {
if (!this.isStarted())
throw new Exception("CongControlTIMER_D parameter can be updated only when SCCP stack is running");
if (value < 500)
value = 500;
if (value > 10000)
value = 10000;
congControl_TIMER_D = value;
this.store();
}
public int getCongControlN() {
return congControl_N;
}
public void setCongControlN(int value) {
congControl_N = value;
this.store();
}
public int getCongControlM() {
return congControl_M;
}
public void setCongControlM(int value) {
congControl_M = value;
this.store();
}
public SccpCongestionControlAlgo getCongControl_Algo() {
return congControl_Algo;
}
public void setCongControl_Algo(SccpCongestionControlAlgo value) throws Exception {
if (!this.isStarted())
throw new Exception("CongControl_Algo parameter can be updated only when SCCP stack is running");
if (value != null)
congControl_Algo = value;
this.store();
}
public boolean isCongControl_blockingOutgoungSccpMessages() {
return congControl_blockingOutgoungSccpMessages;
}
public void setCongControl_blockingOutgoungSccpMessages(boolean value) throws Exception {
if (!this.isStarted())
throw new Exception("CongControl_blockingOutgoungSccpMessages parameter can be updated only when SCCP stack is running");
congControl_blockingOutgoungSccpMessages = value;
this.store();
}
public boolean isRemoveSpc() {
return this.removeSpc;
}
public SccpProtocolVersion getSccpProtocolVersion() {
return this.sccpProtocolVersion;
}
public boolean isPreviewMode() {
return this.previewMode;
}
public int getSstTimerDuration_Min() {
return this.sstTimerDuration_Min;
}
public int getSstTimerDuration_Max() {
return this.sstTimerDuration_Max;
}
public double getSstTimerDuration_IncreaseFactor() {
return this.sstTimerDuration_IncreaseFactor;
}
public int getZMarginXudtMessage() {
return zMarginXudtMessage;
}
public void setZMarginXudtMessage(int zMarginXudtMessage) throws Exception {
if (!this.isStarted())
throw new Exception("ZMarginXudtMessage parameter can be updated only when SCCP stack is running");
// value from 160 to 255 bytes
if (zMarginXudtMessage < 160)
zMarginXudtMessage = 160;
if (zMarginXudtMessage > 255)
zMarginXudtMessage = 255;
this.zMarginXudtMessage = zMarginXudtMessage;
this.store();
}
public int getMaxDataMessage() {
return maxDataMessage;
}
public void setMaxDataMessage(int maxDataMessage) throws Exception {
if (!this.isStarted())
throw new Exception("MaxDataMessage parameter can be updated only when SCCP stack is running");
// from 2560 to 3952 bytes
if (maxDataMessage < 2560)
maxDataMessage = 2560;
if (maxDataMessage > 3952)
maxDataMessage = 3952;
this.maxDataMessage = maxDataMessage;
this.store();
}
public int getReassemblyTimerDelay() {
return this.reassemblyTimerDelay;
}
public void setReassemblyTimerDelay(int reassemblyTimerDelay) throws Exception {
if (!this.isStarted())
throw new Exception("ReassemblyTimerDelay parameter can be updated only when SCCP stack is running");
// from 10 to 20 seconds
if (reassemblyTimerDelay < 10000)
reassemblyTimerDelay = 10000;
if (reassemblyTimerDelay > 20000)
reassemblyTimerDelay = 20000;
this.reassemblyTimerDelay = reassemblyTimerDelay;
this.store();
}
public synchronized int newSegmentationLocalRef() {
return ++this.segmentationLocalRef;
}
public synchronized int newSls() {
if (++this.slsCounter > 255)
this.slsCounter = 0;
return this.slsCounter;
}
public synchronized boolean newSelector() {
if (++this.selectorCounter > 1)
this.selectorCounter = 0;
return (this.selectorCounter == 1);
}
protected void createSLSTable(int maxSls, int minimumBoundThread) {
int stream = 0;
for (int i = 0; i < maxSls; i++) {
if (stream >= minimumBoundThread) {
stream = 0;
}
slsTable[i] = stream++;
}
}
public void start() throws IllegalStateException {
logger.info("Starting ...");
this.persistFile.clear();
if (persistDir != null) {
this.persistFile.append(persistDir).append(File.separator).append(this.name).append("_").append(PERSIST_FILE_NAME);
} else {
persistFile.append(System.getProperty(SCCP_MANAGEMENT_PERSIST_DIR_KEY, System.getProperty(USER_DIR_KEY)))
.append(File.separator).append(this.name).append("_").append(PERSIST_FILE_NAME);
}
logger.info(String.format("SCCP Management configuration file path %s", persistFile.toString()));
try {
this.load();
} catch (FileNotFoundException e) {
logger.warn(String.format("Failed to load the Sccp Management configuration file. \n%s", e.getMessage()));
}
// FIXME: make this configurable
// FIXME: move creation to constructor ?
this.sccpManagement = new SccpManagement(name, sccpProvider, this);
this.sccpRoutingControl = new SccpRoutingControl(sccpProvider, this);
this.sccpCongestionControl = new SccpCongestionControl(sccpManagement, this);
this.sccpManagement.setSccpRoutingControl(sccpRoutingControl);
this.sccpRoutingControl.setSccpManagement(sccpManagement);
this.sccpManagement.setSccpCongestionControl(sccpCongestionControl);
this.router = new RouterImpl(this.name, this);
this.router.setPersistDir(this.persistDir);
this.router.start();
this.sccpResource = new SccpResourceImpl(this.name, this.rspProhibitedByDefault);
this.sccpResource.setPersistDir(this.persistDir);
this.sccpResource.start();
logger.info("Starting routing engine...");
this.sccpRoutingControl.start();
logger.info("Starting management ...");
this.sccpManagement.start();
logger.info("Starting MSU handler...");
this.timerExecutors = Executors.newScheduledThreadPool(1);
// initiating of SCCP delivery executors
// TODO: we do it for ITU standard, may be we may configure it for other standard's (different SLS count) maxSls and
// slsFilter values initiating
int maxSls = 16;
slsFilter = 0x0f;
this.slsTable = new int[maxSls];
this.createSLSTable(maxSls, this.deliveryTransferMessageThreadCount);
this.msgDeliveryExecutors = new ExecutorService[this.deliveryTransferMessageThreadCount];
for (int i = 0; i < this.deliveryTransferMessageThreadCount; i++) {
this.msgDeliveryExecutors[i] = Executors.newFixedThreadPool(1, new DefaultThreadFactory(
"SccpTransit-DeliveryExecutor-" + i));
}
for (FastMap.Entry<Integer, Mtp3UserPart> e = this.mtp3UserParts.head(), end = this.mtp3UserParts.tail(); (e = e
.getNext()) != end;) {
Mtp3UserPart mup = e.getValue();
mup.addMtp3UserPartListener(this);
}
for (SccpManagementEventListener lstr : this.sccpProvider.managementEventListeners) {
try {
lstr.onServiceStarted();
} catch (Throwable ee) {
logger.error("Exception while invoking onServiceStarted", ee);
}
}
this.state = State.RUNNING;
}
public void stop() {
logger.info("Stopping ...");
// stateLock.lock();
// try
// {
this.state = State.IDLE;
// executor = null;
//
// layer3exec = null;
if (this.msgDeliveryExecutors != null) {
for (ExecutorService es : this.msgDeliveryExecutors) {
es.shutdown();
}
this.msgDeliveryExecutors = null;
}
for (SccpManagementEventListener lstr : this.sccpProvider.managementEventListeners) {
try {
lstr.onServiceStopped();
} catch (Throwable ee) {
logger.error("Exception while invoking onServiceStopped", ee);
}
}
for (FastMap.Entry<Integer, Mtp3UserPart> e = this.mtp3UserParts.head(), end = this.mtp3UserParts.tail(); (e = e
.getNext()) != end;) {
Mtp3UserPart mup = e.getValue();
mup.removeMtp3UserPartListener(this);
}
logger.info("Stopping management...");
this.sccpManagement.stop();
logger.info("Stopping routing engine...");
this.sccpRoutingControl.stop();
logger.info("Stopping MSU handler...");
this.sccpResource.stop();
this.router.stop();
synchronized (reassemplyCache) {
this.timerExecutors.shutdownNow();
reassemplyCache.clear();
}
this.store();
// }finally
// {
// stateLock.unlock();
// }
}
public boolean isStarted() {
return this.state == State.RUNNING;
}
public Router getRouter() {
return this.router;
}
public SccpResource getSccpResource() {
return sccpResource;
}
protected enum State {
IDLE, CONFIGURED, RUNNING;
}
protected void send(SccpDataMessageImpl message) throws Exception {
if (this.state != State.RUNNING) {
logger.error("Trying to send SCCP message from SCCP user but SCCP stack is not RUNNING");
return;
}
if (message.getCalledPartyAddress() == null || message.getCallingPartyAddress() == null || message.getData() == null
|| message.getData().length == 0) {
throw new IOException("Message to send must has filled CalledPartyAddress, CallingPartyAddress and data fields");
}
try {
this.sccpRoutingControl.routeMssgFromSccpUser(message);
} catch (Exception e) {
// log here Exceptions from MTP3 level
logger.error("IOException when sending the message to MTP3 level: " + e.getMessage(), e);
throw e;
}
}
protected int getMaxUserDataLength(SccpAddress calledPartyAddress, SccpAddress callingPartyAddress, int msgNetworkId) {
GlobalTitle gt = calledPartyAddress.getGlobalTitle();
int dpc = calledPartyAddress.getSignalingPointCode();
int ssn = calledPartyAddress.getSubsystemNumber();
if (calledPartyAddress.getAddressIndicator().isPCPresent()) {
if (this.router.spcIsLocal(dpc)) {
if (ssn > 0) {
// local destination - unlimited length
return this.getMaxDataMessage();
} else if (gt != null) {
return getMaxUserDataLengthForGT(calledPartyAddress, callingPartyAddress, msgNetworkId);
} else {
return 0;
}
} else {
return getMaxUserDataLengthForDpc(dpc, calledPartyAddress, callingPartyAddress);
}
} else {
if (gt != null) {
return getMaxUserDataLengthForGT(calledPartyAddress, callingPartyAddress, msgNetworkId);
} else {
return 0;
}
}
}
private int getMaxUserDataLengthForDpc(int dpc, SccpAddress calledPartyAddress, SccpAddress callingPartyAddress) {
LongMessageRule lmr = this.router.findLongMessageRule(dpc);
LongMessageRuleType lmrt = LongMessageRuleType.LONG_MESSAGE_FORBBIDEN;
if (lmr != null)
lmrt = lmr.getLongMessageRuleType();
Mtp3ServiceAccessPoint sap = this.router.findMtp3ServiceAccessPoint(dpc, 0);
if (sap == null) {
return 0;
}
Mtp3UserPart mup = this.getMtp3UserPart(sap.getMtp3Id());
if (mup == null) {
return 0;
}
try {
int fieldsLen = 0;
byte[] cdp = ((SccpAddressImpl) calledPartyAddress).encode(isRemoveSpc(), this.getSccpProtocolVersion());
byte[] cnp = ((SccpAddressImpl) callingPartyAddress).encode(isRemoveSpc(), this.getSccpProtocolVersion());
switch (lmrt) {
case LONG_MESSAGE_FORBBIDEN:
fieldsLen = calculateUdtFieldsLengthWithoutData(cdp.length, cnp.length);
break;
case LUDT_ENABLED:
case LUDT_ENABLED_WITH_SEGMENTATION:
fieldsLen = calculateLudtFieldsLengthWithoutData(cdp.length, cnp.length, true, true);
break;
case XUDT_ENABLED:
fieldsLen = calculateXudtFieldsLengthWithoutData(cdp.length, cnp.length, true, true);
int fieldsLen2 = calculateXudtFieldsLengthWithoutData2(cdp.length, cnp.length);
if (fieldsLen > fieldsLen2)
fieldsLen = fieldsLen2;
break;
}
int availLen = mup.getMaxUserDataLength(dpc) - fieldsLen;
if ((lmrt == LongMessageRuleType.LONG_MESSAGE_FORBBIDEN || lmrt == LongMessageRuleType.XUDT_ENABLED)
&& availLen > 255)
availLen = 255;
if (lmrt == LongMessageRuleType.XUDT_ENABLED)
availLen *= 16;
if (availLen > this.getMaxDataMessage())
availLen = this.getMaxDataMessage();
return availLen;
} catch (Exception e) {
// this can not occur
// dont be so sure!
e.printStackTrace();
return 0;
}
}
private int getMaxUserDataLengthForGT(SccpAddress calledPartyAddress, SccpAddress callingPartyAddress, int msgNetworkId) {
Rule rule = this.router.findRule(calledPartyAddress, callingPartyAddress, false, msgNetworkId);
if (rule == null) {
return 0;
}
SccpAddress translationAddressPri = this.router.getRoutingAddress(rule.getPrimaryAddressId());
if (translationAddressPri == null) {
return 0;
}
return getMaxUserDataLengthForDpc(translationAddressPri.getSignalingPointCode(), calledPartyAddress,
callingPartyAddress);
}
protected void broadcastChangedSsnState(int affectedSsn, boolean inService) {
this.sccpManagement.broadcastChangedSsnState(affectedSsn, inService);
}
public void removeAllResourses() {
if (this.state != State.RUNNING) {
return;
}
this.router.removeAllResourses();
this.sccpResource.removeAllResourses();
for (SccpManagementEventListener lstr : this.sccpProvider.managementEventListeners) {
try {
lstr.onRemoveAllResources();
} catch (Throwable ee) {
logger.error("Exception while invoking onRemoveAllResources", ee);
}
}
}
public void onMtp3PauseMessage(Mtp3PausePrimitive msg) {
logger.warn(String.format("Rx : %s", msg));
if (this.state != State.RUNNING) {
logger.error("Cannot consume MTP3 PASUE message as SCCP stack is not RUNNING");
return;
}
sccpManagement.handleMtp3Pause(msg.getAffectedDpc());
}
public void onMtp3ResumeMessage(Mtp3ResumePrimitive msg) {
logger.warn(String.format("Rx : %s", msg));
if (this.state != State.RUNNING) {
logger.error("Cannot consume MTP3 RESUME message as SCCP stack is not RUNNING");
return;
}
sccpManagement.handleMtp3Resume(msg.getAffectedDpc());
}
public void onMtp3StatusMessage(Mtp3StatusPrimitive msg) {
int affectedDpc = msg.getAffectedDpc();
// we are making of announcing of MTP-STATUS only each 10 seconds
Date lastNotice;
if (msg.getCause() == Mtp3StatusCause.SignallingNetworkCongested) {
lastNotice = lastCongNotice.get(affectedDpc);
if (lastNotice == null) {
lastCongNotice.put(affectedDpc, new Date());
logger.warn(String.format("Rx : %s (one message in %d seconds)", msg, STATUS_MSG_LOGGING_INTERVAL_MILLISEC_UNAVAIL));
} else {
if (System.currentTimeMillis() - lastNotice.getTime() > STATUS_MSG_LOGGING_INTERVAL_MILLISEC_CONG) {
lastNotice.setTime(System.currentTimeMillis());
logger.warn(String.format("Rx : %s (one message in %d seconds)", msg, STATUS_MSG_LOGGING_INTERVAL_MILLISEC_UNAVAIL));
}
}
} else {
lastNotice = lastUserPartUnavailNotice.get(affectedDpc);
if (lastNotice == null) {
lastUserPartUnavailNotice.put(affectedDpc, new Date());
logger.warn(String.format("Rx : %s (one message in %d seconds)", msg, STATUS_MSG_LOGGING_INTERVAL_MILLISEC_UNAVAIL));
} else {
if (System.currentTimeMillis() - lastNotice.getTime() > STATUS_MSG_LOGGING_INTERVAL_MILLISEC_UNAVAIL) {
lastNotice.setTime(System.currentTimeMillis());
logger.warn(String.format("Rx : %s (one message in %d seconds)", msg, STATUS_MSG_LOGGING_INTERVAL_MILLISEC_UNAVAIL));
}
}
}
if (this.state != State.RUNNING) {
logger.error("Cannot consume MTP3 STATUS message as SCCP stack is not RUNNING");
return;
}
sccpManagement.handleMtp3Status(msg.getCause(), affectedDpc, msg.getCongestionLevel());
}
@Override
public void onMtp3EndCongestionMessage(Mtp3EndCongestionPrimitive msg) {
int affectedDpc = msg.getAffectedDpc();
logger.warn(String.format("Rx : %s", msg));
sccpManagement.handleMtp3EndCongestion(affectedDpc);
}
public void onMtp3TransferMessage(Mtp3TransferPrimitive mtp3Msg) {
if (this.state != State.RUNNING) {
logger.error("Received MTP3TransferPrimitive from lower layer but SCCP stack is not RUNNING");
return;
}
SccpMessageImpl msg = null;
int dpc = mtp3Msg.getDpc();
int opc = mtp3Msg.getOpc();
try {
// checking if incoming dpc is local
if (!this.isPreviewMode() && !this.router.spcIsLocal(dpc)) {
// incoming dpc is not local - trying to find the target SAP and
// send a message to MTP3 (MTP transit function)
int sls = mtp3Msg.getSls();
RemoteSignalingPointCode remoteSpc = this.getSccpResource().getRemoteSpcByPC(dpc);
Mtp3ServiceAccessPoint sap = this.router.findMtp3ServiceAccessPoint(opc, sls);
if (remoteSpc == null) {
if (logger.isEnabledFor(Level.WARN)) {
logger.warn(String.format("Incoming Mtp3 Message for nonlocal dpc=%d. But RemoteSpc is not found", dpc));
}
return;
}
if (remoteSpc.isRemoteSpcProhibited()) {
if (logger.isEnabledFor(Level.WARN)) {
logger.warn(String
.format("Incoming Mtp3 Message for nonlocal dpc=%d. But RemoteSpc is Prohibited", dpc));
}
// TODO: ***** SSP should we send SSP message to a peer ?
return;
}
if (remoteSpc.getCurrentRestrictionLevel() > 1) {
if (logger.isEnabledFor(Level.WARN)) {
logger.warn(String
.format("Incoming Mtp3 Message for nonlocal dpc=%d. But RemoteSpc is Congested", dpc));
}
// TODO: ***** SSC should we send SSC message to a peer ?
return;
}
Mtp3ServiceAccessPoint sap2 = this.router.findMtp3ServiceAccessPoint(dpc, sls);
if (sap2 == null) {
if (logger.isEnabledFor(Level.WARN)) {
logger.warn(String.format("Incoming Mtp3 Message for nonlocal dpc=%d / sls=%d. But SAP is not found",
dpc, sls));
}
return;
}
Mtp3UserPart mup = this.getMtp3UserPart(sap2.getMtp3Id());
if (mup == null) {
if (logger.isEnabledFor(Level.WARN)) {
logger.warn(String.format(
"Incoming Mtp3 Message for nonlocal dpc=%d / sls=%d. no matching Mtp3UserPart found", dpc, sls));
}
return;
}
mup.sendMessage(mtp3Msg);
return;
}
Mtp3ServiceAccessPoint sap = this.router.findMtp3ServiceAccessPointForIncMes(dpc, opc);
int networkId = 0;
if (sap == null) {
if (logger.isEnabledFor(Level.WARN)) {
logger.warn(String.format("Incoming Mtp3 Message for local address for localPC=%d, remotePC=%d, sls=%d. But SAP is not found for localPC", dpc, opc, mtp3Msg.getSls()));
}
} else {
networkId = sap.getNetworkId();
}
// process only SCCP messages
if (mtp3Msg.getSi() != Mtp3._SI_SERVICE_SCCP) {
logger.warn(String
.format("Received Mtp3TransferPrimitive from lower layer with Service Indicator=%d which is not SCCP. Dropping this message",
mtp3Msg.getSi()));
return;
}
ByteArrayInputStream bais = new ByteArrayInputStream(mtp3Msg.getData());
DataInputStream in = new DataInputStream(bais);
int mt = in.readUnsignedByte();
msg = ((MessageFactoryImpl) sccpProvider.getMessageFactory()).createMessage(mt, mtp3Msg.getOpc(), mtp3Msg.getDpc(), mtp3Msg.getSls(), in,
this.sccpProtocolVersion, networkId);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Rx : SCCP message from MTP %s", msg));
}
// when segmented messages - make a reassembly operation
if (msg instanceof SccpSegmentableMessageImpl) {
SccpSegmentableMessageImpl sgmMsg = (SccpSegmentableMessageImpl) msg;
SegmentationImpl segm = (SegmentationImpl) sgmMsg.getSegmentation();
if (segm != null) {
// segmentation info is present - segmentation is possible
if (segm.isFirstSegIndication() && segm.getRemainingSegments() == 0) {
// the single segment - no reassembly is needed
sgmMsg.setReceivedSingleSegment();
} else {
// multiple segments - reassembly is needed
if (segm.isFirstSegIndication()) {
// first segment
sgmMsg.setReceivedFirstSegment();
MessageReassemblyProcess msp = new MessageReassemblyProcess(segm.getSegmentationLocalRef(),
sgmMsg.getCallingPartyAddress());
synchronized (this.reassemplyCache) {
this.reassemplyCache.put(msp, sgmMsg);
}
sgmMsg.setMessageReassemblyProcess(msp);
msp.startTimer();
return;
} else {
// nonfirst segment
MessageReassemblyProcess msp = new MessageReassemblyProcess(segm.getSegmentationLocalRef(),
sgmMsg.getCallingPartyAddress());
SccpSegmentableMessageImpl sgmMsgFst = null;
synchronized (this.reassemplyCache) {
sgmMsgFst = this.reassemplyCache.get(msp);
}
if (sgmMsgFst == null) {
// previous segments cache is not found -
// discard a segment
if (logger.isEnabledFor(Level.WARN)) {
logger.warn(String
.format("Reassembly function failure: received a non first segment without the first segement having recieved. SccpMessageSegment=%s",
msg));
}
return;
}
if (sgmMsgFst.getRemainingSegments() - 1 != segm.getRemainingSegments()) {
// segments bad order
synchronized (this.reassemplyCache) {
this.reassemplyCache.remove(msp);
MessageReassemblyProcess mspMain = sgmMsgFst.getMessageReassemblyProcess();
if (mspMain != null)
mspMain.stopTimer();
}
if (logger.isEnabledFor(Level.WARN)) {
logger.warn(String
.format("Reassembly function failure: when receiving a next segment message order is missing. SccpMessageSegment=%s",
msg));
}
this.sccpRoutingControl.sendSccpError(sgmMsgFst, ReturnCauseValue.CANNOT_REASEMBLE);
return;
}
if (sgmMsgFst.getRemainingSegments() == 1) {
// last segment
synchronized (this.reassemplyCache) {
MessageReassemblyProcess mspMain = sgmMsgFst.getMessageReassemblyProcess();
if (mspMain != null)
mspMain.stopTimer();
this.reassemplyCache.remove(msp);
}
if (sgmMsgFst.getRemainingSegments() != 1)
return;
sgmMsgFst.setReceivedNextSegment(sgmMsg);
msg = sgmMsgFst;
} else {
// not last segment
sgmMsgFst.setReceivedNextSegment(sgmMsg);
return;
}
}
}
}
}
if (msg instanceof SccpAddressedMessageImpl) {
// CR or connectionless messages
SccpAddressedMessageImpl msgAddr = (SccpAddressedMessageImpl) msg;
// adding OPC into CallingPartyAddress if it is absent there and
// "RouteOnSsn"
SccpAddress addr = msgAddr.getCallingPartyAddress();
if (addr != null
&& addr.getAddressIndicator().getRoutingIndicator() == RoutingIndicator.ROUTING_BASED_ON_DPC_AND_SSN) {
if (!addr.getAddressIndicator().isPCPresent()) {
msgAddr.setCallingPartyAddress(new SccpAddressImpl(RoutingIndicator.ROUTING_BASED_ON_DPC_AND_SSN, null, msgAddr.getIncomingOpc(), addr
.getSubsystemNumber()));
}
}
sccpRoutingControl.routeMssgFromMtp(msgAddr);
} else {
// TODO: implement non-addresses message processing (these are
// connected-oriented messages in the connected phase)
logger.warn(String
.format("Rx SCCP message which is not instance of SccpAddressedMessage or SccpSegmentableMessage. Will be dropped. Message=",
msg));
}
} catch (Exception e) {
logger.error("IOException while decoding SCCP message: " + e.getMessage(), e);
}
}
public FastMap<Integer, NetworkIdState> getNetworkIdList(int affectedPc) {
return router.getNetworkIdList(affectedPc);
}
public class MessageReassemblyProcess implements Runnable {
private int segmentationLocalRef;
private SccpAddress callingPartyAddress;
private Future timer;
public MessageReassemblyProcess(int segmentationLocalRef, SccpAddress callingPartyAddress) {
this.segmentationLocalRef = segmentationLocalRef;
this.callingPartyAddress = callingPartyAddress;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof MessageReassemblyProcess))
return false;
MessageReassemblyProcess x = (MessageReassemblyProcess) obj;
if (this.segmentationLocalRef != x.segmentationLocalRef)
return false;
if (this.callingPartyAddress == null || x.callingPartyAddress == null)
return false;
return this.callingPartyAddress.equals(x.callingPartyAddress);
}
@Override
public int hashCode() {
return this.segmentationLocalRef;
}
public void startTimer() {
this.timer = timerExecutors.schedule(this, reassemblyTimerDelay, TimeUnit.MILLISECONDS);
}
public void stopTimer() {
if (this.timer != null) {
this.timer.cancel(false);
this.timer = null;
}
}
public void run() {
SccpSegmentableMessageImpl msg = null;
synchronized (reassemplyCache) {
msg = reassemplyCache.remove(this);
if (msg == null)
return;
msg.cancelSegmentation();
}
try {
sccpRoutingControl.sendSccpError(msg, ReturnCauseValue.CANNOT_REASEMBLE);
} catch (Exception e) {
logger.warn("IOException when sending an error message", e);
}
}
}
/**
* Persist
*/
public void store() {
// TODO : Should we keep reference to Objects rather than recreating
// everytime?
try {
XMLObjectWriter writer = XMLObjectWriter.newInstance(new FileOutputStream(persistFile.toString()));
writer.setBinding(binding);
// Enables cross-references.
// writer.setReferenceResolver(new XMLReferenceResolver());
writer.setIndentation(TAB_INDENT);
writer.write(this.zMarginXudtMessage, Z_MARGIN_UDT_MSG, Integer.class);
writer.write(this.reassemblyTimerDelay, REASSEMBLY_TIMER_DELAY, Integer.class);
writer.write(this.maxDataMessage, MAX_DATA_MSG, Integer.class);
writer.write(this.removeSpc, REMOVE_SPC, Boolean.class);
writer.write(this.previewMode, PREVIEW_MODE, Boolean.class);
if (this.sccpProtocolVersion != null)
writer.write(this.sccpProtocolVersion.toString(), SCCP_PROTOCOL_VERSION, String.class);
writer.write(this.congControl_TIMER_A, CONG_CONTROL_TIMER_A, Integer.class);
writer.write(this.congControl_TIMER_D, CONG_CONTROL_TIMER_D, Integer.class);
if (this.congControl_Algo != null)
writer.write(this.congControl_Algo.toString(), CONG_CONTROL_ALGO, String.class);
writer.write(this.congControl_blockingOutgoungSccpMessages, CONG_CONTROL_BLOCKING_OUTGOUNG_SCCP_MESSAGES,
Boolean.class);
writer.write(this.sstTimerDuration_Min, SST_TIMER_DURATION_MIN, Integer.class);
writer.write(this.sstTimerDuration_Max, SST_TIMER_DURATION_MAX, Integer.class);
writer.write(this.sstTimerDuration_IncreaseFactor, SST_TIMER_DURATION_INCREASE_FACTOR, Double.class);
writer.close();
} catch (Exception e) {
this.logger.error(
String.format("Error while persisting the Sccp Resource state in file=%s", persistFile.toString()), e);
}
}
/**
* Load and create LinkSets and Link from persisted file
*
* @throws Exception
*/
protected void load() throws FileNotFoundException {
XMLObjectReader reader = null;
try {
reader = XMLObjectReader.newInstance(new FileInputStream(persistFile.toString()));
reader.setBinding(binding);
Integer vali = reader.read(Z_MARGIN_UDT_MSG, Integer.class);
if (vali != null)
this.zMarginXudtMessage = vali;
vali = reader.read(REASSEMBLY_TIMER_DELAY, Integer.class);
if (vali != null)
this.reassemblyTimerDelay = vali;
vali = reader.read(MAX_DATA_MSG, Integer.class);
if (vali != null)
this.maxDataMessage = vali;
Boolean volb = reader.read(REMOVE_SPC, Boolean.class);
if (volb != null)
this.removeSpc = volb;
volb = reader.read(PREVIEW_MODE, Boolean.class);
if (volb != null)
this.previewMode = volb;
volb = reader.read(RESERVED_FOR_NATIONAL_USE_VALUE_ADDRESS_INDICATOR, Boolean.class);
String s1 = reader.read(SCCP_PROTOCOL_VERSION, String.class);
if (s1 != null)
this.sccpProtocolVersion = Enum.valueOf(SccpProtocolVersion.class, s1);
vali = reader.read(CONG_CONTROL_TIMER_A, Integer.class);
if (vali != null)
this.congControl_TIMER_A = vali;
vali = reader.read(CONG_CONTROL_TIMER_D, Integer.class);
if (vali != null)
this.congControl_TIMER_D = vali;
s1 = reader.read(CONG_CONTROL_ALGO, String.class);
if (s1 != null)
this.congControl_Algo = Enum.valueOf(SccpCongestionControlAlgo.class, s1);
volb = reader.read(CONG_CONTROL_BLOCKING_OUTGOUNG_SCCP_MESSAGES, Boolean.class);
if (volb != null)
this.congControl_blockingOutgoungSccpMessages = volb;
vali = reader.read(SST_TIMER_DURATION_MIN, Integer.class);
if (vali != null)
this.sstTimerDuration_Min = vali;
vali = reader.read(SST_TIMER_DURATION_MAX, Integer.class);
if (vali != null)
this.sstTimerDuration_Max = vali;
Double vald = reader.read(SST_TIMER_DURATION_INCREASE_FACTOR, Double.class);
if (vald != null)
this.sstTimerDuration_IncreaseFactor = vald;
reader.close();
} catch (XMLStreamException ex) {
// this.logger.info(
// "Error while re-creating Linksets from persisted file", ex);
}
}
}