/* * JBoss, Home of Professional Open Source * Copyright 2011, Red Hat, Inc. 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.mtp; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javolution.util.FastList; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.mobicents.protocols.ss7.scheduler.Scheduler; import org.mobicents.protocols.ss7.scheduler.Task; import org.mobicents.protocols.stream.api.SelectorKey; import org.mobicents.protocols.stream.api.SelectorProvider; import org.mobicents.protocols.stream.api.StreamSelector; /** * * @author kulikov * @author baranowb */ public class Mtp3 implements Runnable { public static final int TIMEOUT_T1_SLTM = 120; public static final int TIMEOUT_T2_SLTM = 900; private static final int LINK_MANAGEMENT = 0; private static final int LINK_TESTING = 1; public static final int _SI_SERVICE_SCCP = 3; public static final int _SI_SERVICE_ISUP = 5; private Mtp3Listener mtp3Listener; /** * Flag indicating if we should notify upper layer */ private boolean l4IsUp = false; // FIXME: MOVE THIS TO LINKSET? protected volatile boolean started = false; /** defautl value of SSI */ public static final int DEFAULT_NI = 2;// NATIONAL, as default. private int dpc; private int opc; private int ni = DEFAULT_NI << 2; // << cause its faster for checks. /** * Local byte[], in which we forge messages. */ private byte[] localFrame = new byte[279]; /** List of signaling channels wrapped with MTP2 */ private List<Mtp2> links = new ArrayList(); /** Active links */ private Linkset linkset = new Linkset(); // ss7 has subservice as 1, q704 shows the same iirc // private int subservice = -1; // private int service; // ////////////////////////// // SLTM pattern for inits // // ////////////////////////// private static final byte[] SLTM_PATTERN = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0F }; protected String name; private StreamSelector selector; private Scheduler scheduler; private static final Logger logger = Logger.getLogger(Mtp3.class); // public Mtp3Impl(String name, Mtp1 layer1) { public Mtp3(String name, Scheduler scheduler) { this.scheduler = scheduler; this.name = name; try { selector = SelectorProvider.getSelector("org.mobicents.ss7.hardware.dahdi.Selector"); } catch (Exception e) { e.printStackTrace(); } } public void setScheduler(Scheduler scheduler) { if (scheduler != null) this.scheduler = scheduler; } /** * Assigns originated point code * * @param opc the originated point code */ public void setOpc(int opc) { this.opc = opc; } /** * Assigns destination point code. * * @param dpc destination point code in decimal format. */ public void setDpc(int dpc) { this.dpc = dpc; } /** * @return the dpc */ public int getDpc() { return dpc; } /** * @return the opc */ public int getOpc() { return opc; } /** * Sets network indicator to be used as part of SIO( actually SSI). It Accepts 2 bit integer. * * @param ni */ public void setNetworkIndicator(int ni) { this.ni = (0x03 & ni) << 2; } public int getNetworkIndicator() { return this.ni >> 2; } /** * (non-Javadoc) * * @see org.mobicents.protocols.ss7.mtp.Mtp3#addMtp3Listener(org.mobicents.protocols.ss7.mtp.Mtp3Listener) */ public void addMtp3Listener(Mtp3Listener lst) { if (this.mtp3Listener != null) { throw new IllegalStateException("Listener already present."); } this.mtp3Listener = lst; } /* * (non-Javadoc) * * @see org.mobicents.protocols.ss7.mtp.Mtp3#removeMtp3Listener(org.mobicents.protocols.ss7.mtp.Mtp3Listener) */ public void removeMtp3Listener(Mtp3Listener lst) { if (lst != this.mtp3Listener) { throw new IllegalArgumentException("Wrong listener passed. Its not registered in this object!"); } this.mtp3Listener = null; } /* * (non-Javadoc) * * @see org.mobicents.protocols.ss7.mtp.Mtp3#setLinks(java.util.List) */ public void setLinks(List<Mtp2> channels) { for (Mtp2 link : channels) { if (link != null) { link.mtp3 = this; this.links.add(link); } } } public void addLink(Mtp2 mtp2) { mtp2.mtp3 = this; this.links.add(mtp2); } public void clearLinks() { this.links.clear(); } /** * Starts linkset. * * @throws java.io.IOException */ public void start() throws IOException { // starting links for (Mtp2 link : links) { link.start(); } } public void stop() throws IOException { started = false; for (Mtp2 link : links) { // selector.unregister(link); link.stop(); } } public void run() { try { FastList<SelectorKey> selected = selector.selectNow(StreamSelector.OP_READ, 20); for (FastList.Node<SelectorKey> n = selected.head(), end = selected.tail(); (n = n.getNext()) != end;) { ((Mtp2) ((Mtp1) n.getValue().getStream()).getLink()).doRead(); } selected = selector.selectNow(StreamSelector.OP_WRITE, 20); for (FastList.Node<SelectorKey> n = selected.head(), end = selected.tail(); (n = n.getNext()) != end;) { ((Mtp2) ((Mtp1) n.getValue().getStream()).getLink()).doWrite(); } } catch (Exception e) { e.printStackTrace(); } } /** * This method is called when new MSU is detected. * * @param sio service information octet. * @param msg service information field; */ public void onMessage(Mtp2Buffer rxFrame, Mtp2 mtp2) { // | ----------------------- TO L4 --------------------------- | // | --------------------- SIF ------------------------ | // FSN BSN LEN SIO DPC/OPC,SLS HEADING LEN // b1 b0 0e 01 01 80 00 00 21 70 01 02 03 04 05 06 0f CRC CRC int sio = rxFrame.frame[3] & 0xFF; int subserviceIndicator = (sio >> 4) & 0x0F; int serviceIndicator = sio & 0x0F; int ni = (subserviceIndicator & 0x0C); // local NI(network Indicator) form msg., 0x0C, since we store it as shifted // value. // int dpc = (sif[0] & 0xff | ((sif[1] & 0x3f) << 8)); // int opc = ((sif[1] & 0xC0) >> 6) | ((sif[2] & 0xff) << 2) | ((sif[3] // & 0x0f) << 10); // int sls = (sif[3] & 0xf0) >>> 4; int dpc = dpc(rxFrame.frame, 4); // 1 - cause sif contains sio, even though its also passed as arg. int opc = opc(rxFrame.frame, 4); int sls = sls(rxFrame.frame, 4); // check SSI, Q.704 Figure 25, seems like if its bad, we discard. if (this.ni != ni) { if (logger.isEnabledFor(Level.ERROR)) { logger.error(String.format("(%s) Received MSSU with bad SSI, discarding! ni:" + ni + " thisni:" + this.ni + " [si=" + serviceIndicator + ",ssi=" + subserviceIndicator + ", dpc=" + dpc + ", opc=" + opc + ", sls=" + sls + "] data: ", mtp2.getName()) + Arrays.toString(rxFrame.frame)); } return; } switch (serviceIndicator) { case LINK_MANAGEMENT: int h0 = rxFrame.frame[8] & 0x0f; int h1 = (rxFrame.frame[8] & 0xf0) >>> 4; if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) Signalling network management", mtp2.getName())); } if (h0 == 0) { if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) Changeover management", mtp2.getName())); } } else if (h0 == 7 && h1 == 1) { if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) TRA received", mtp2.getName())); } } // FIX ME break; case LINK_TESTING: h0 = rxFrame.frame[8] & 0x0f; h1 = (rxFrame.frame[8] & 0xf0) >>> 4; int len = (rxFrame.frame[9] & 0xf0) >>> 4; if (h0 == 1 && h1 == 1) { if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) Received SLTM", mtp2.getName())); } // receive SLTM from remote end // create response // change order of opc/dpc in SLTA ! writeRoutingLabel(this.localFrame, sio, this.ni, sls, opc, dpc); // slta[0] = (byte) sio; this.localFrame[5] = 0x021; // +1 cause we copy LEN byte also. System.arraycopy(rxFrame.frame, 9, this.localFrame, 6, len + 1); if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) Responding with SLTA", mtp2.getName())); } mtp2.send(this.localFrame, len + 9); } else if (h0 == 1 && h1 == 2) { // receive SLTA from remote end if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) Received SLTA", mtp2.getName())); } // checking pattern if (checkPattern(rxFrame, len, SLTM_PATTERN)) { // test message is acknowledged mtp2.sltmTest.ack(); // notify top layer that link is up if (!l4IsUp) { l4IsUp = true; linkUp(mtp2); } } else { if (logger.isEnabledFor(Level.WARN)) { logger.warn("SLTA pattern does not match: \n" + Arrays.toString(rxFrame.frame) + "\n" + Arrays.toString(SLTM_PATTERN)); } } } else { if (logger.isEnabledFor(Level.WARN)) { logger.warn(String.format("(%s) Unexpected message type", mtp2.getName())); } } break; case _SI_SERVICE_SCCP: if (logger.isDebugEnabled()) { logger.debug("Received SCCP MSU"); } if (mtp3Listener != null) { // | ----------------------- TO L4 --------------------------- | // | --------------------- SIF ------------------------ | // FSN BSN LEN SIO DPC/OPC,SLS HEADING LEN // b1 b0 0e 01 01 80 00 00 21 70 01 02 03 04 05 06 0f CRC CRC byte[] messageBuffer = new byte[rxFrame.len - 5]; // -5 = FSN(1) + BSN(1) + LEN(1) +2xCRC(1) System.arraycopy(rxFrame.frame, 3, messageBuffer, 0, rxFrame.len - 5); // messageBuffer[0] = (byte) sio; try { mtp3Listener.receive(messageBuffer); } catch (Exception e) { e.printStackTrace(); } } break; case _SI_SERVICE_ISUP: if (logger.isDebugEnabled()) { logger.debug("Received ISUP MSU"); } if (mtp3Listener != null) { byte[] messageBuffer = new byte[rxFrame.len - 5]; System.arraycopy(rxFrame.frame, 3, messageBuffer, 0, rxFrame.len - 5); // messageBuffer[0] = (byte) sio; try { mtp3Listener.receive(messageBuffer); } catch (Exception e) { e.printStackTrace(); } } break; default: if (logger.isEnabledFor(Level.WARN)) { logger.warn("Received MSU for UNKNOWN SERVICE!!!!!!!!!!!: " + Utils.dump(rxFrame.frame, rxFrame.len, false)); } break; } } /** * Selects link for transmission using link selection indicator. * * @param sls signaling link selection indicator. * @return selected link. */ private Mtp2 selectLink(byte sls) { return linkset.select(sls); } public boolean send(byte[] msg, int len) { // method expects proper message, lets pray its ok :/ - this is a way to be aligned with dialogic cards. // selecting link using sls // get sls; byte sls = (byte) sls(msg, 1); Mtp2 link = this.selectLink(sls); if (link == null) { return false; } return link.send(msg, len); } public void linkInService(Mtp2 link) { // restart traffic if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) Sending TRA(Traffic Restart Allowed) message", link.getName())); } if (link.mtp2Listener != null) { link.mtp2Listener.linkInService(); } restartTraffic(link); // create and assign Tester; if (link.sltmTest == null) { SLTMTest tester = new SLTMTest(link, scheduler); link.sltmTest = (tester); // start tester if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) Starting link test procedure(SLTM/SLTA)", link.getName())); } tester.start(); } else { link.sltmTest.start(); } } /** * Notify that link is up. * * @param link */ private void linkUp(Mtp2 link) { if (link.mtp2Listener != null) { link.mtp2Listener.linkUp(); } linkset.add(link); if (linkset.isActive() && this.mtp3Listener != null) { try { mtp3Listener.linkUp(); } catch (Exception e) { e.printStackTrace(); } } if (logger.isInfoEnabled()) { logger.info(String.format("(%s) Link now IN_SERVICE", link.getName())); } } public void linkFailed(Mtp2 link) { // Call Listener if (link.mtp2Listener != null) { link.mtp2Listener.linkFailed(); } // FIXME: add debug or trace? // remove this link from list of active links linkset.remove(link); // restart initial alignment after T17 expires link.fail(); // notify mtp user part if (!linkset.isActive()) { l4IsUp = false; if (mtp3Listener != null) { try { mtp3Listener.linkDown(); } catch (Exception e) { e.printStackTrace(); } } } } /* * (non-Javadoc) * * @see org.mobicents.protocols.ss7.mtp.Mtp2Listener#registerLink(org.mobicents.protocols.ss7.mtp.Mtp2) */ public void registerLink(Mtp2 mtp2) { try { mtp2.getLayer1().register(selector); } catch (IOException ex) { ex.printStackTrace(); } } /* * (non-Javadoc) * * @see org.mobicents.protocols.ss7.mtp.Mtp2Listener#unregisterLink(org.mobicents.protocols.ss7.mtp.Mtp2) */ public void unregisterLink(Mtp2 mtp2) { } private void restartTraffic(Mtp2 link) { writeRoutingLabel(this.localFrame, 0, this.ni, 0, dpc, opc); // H0 and H1, see Q.704 section 15.11.2+ this.localFrame[5] = 0x17; link.send(this.localFrame, 6); } private static final int PATTERN_OFFSET = 10; private static final int PATTERN_LEN_OFFSET = PATTERN_OFFSET + 2; // +2 becuase frame.len contains 2B for CRC private boolean checkPattern(Mtp2Buffer frame, int sltmLen, byte[] pattern) { if (sltmLen != pattern.length) { return false; } for (int i = 0; i < pattern.length; i++) { if (frame.frame[i + PATTERN_OFFSET] != pattern[i]) { return false; } } return true; } /** * @param i * @return */ protected class SLTMTest extends Task { private Mtp2 link; private int tryCount; // SLTM message buffer; private byte[] sltm = new byte[7 + SLTM_PATTERN.length]; // this has to be separate, cause its async to Mtp3.send private int ttl = 0; private boolean started = false; private SLTMTest(Mtp2 link, Scheduler scheduler) { super(scheduler); this.link = link; } /** * */ public void start() { this.started = true; // reset count of tries tryCount = 0; this.activate(true); // sending test message to the remote terminal ttl = Mtp3.TIMEOUT_T1_SLTM; ping(); scheduler.submitHeatbeat(this); } public void stop() { this.started = false; // disable handler cancel(); } public int getQueueNumber() { return scheduler.HEARTBEAT_QUEUE; } /** * This methods should be called to acknowledge that current tests is passed. * */ public void ack() { // disable current awaiting handler cancel(); // reset number of tryies; tryCount = -1; // shcedule next ping ttl = Mtp3.TIMEOUT_T2_SLTM; scheduler.submitHeatbeat(this); if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) SLTM acknowledged, Link test passed", link.getName())); } } /** * Sends SLTM message using this link. * * @param timeout the amount of time in millesecond for awaiting response. */ public void ping() { // prepearing test message writeRoutingLabel(sltm, 0x01, ni, link.getSls(), dpc, opc); // sltm[0] = (byte) 0x01; sltm[5] = 0x11; sltm[6] = (byte) (SLTM_PATTERN.length << 4); System.arraycopy(SLTM_PATTERN, 0, sltm, 7, SLTM_PATTERN.length); // sending test message link.send(sltm, sltm.length); // incremeting number of tries. tryCount++; // scheduling timeout if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) SLTM sent, try number = %d", link.getName(), tryCount)); } } public long perform() { if (this.started) return 0; if (ttl > 0) { ttl--; return 0; } switch (tryCount) { case -1: // sending first message ttl = Mtp3.TIMEOUT_T1_SLTM; ping(); scheduler.submitHeatbeat(this); break; case 0: // first message was not answered, sending second ttl = Mtp3.TIMEOUT_T1_SLTM; ping(); scheduler.submitHeatbeat(this); break; case 1: if (logger.isDebugEnabled()) { logger.debug(String.format("(%s) SLTM message was not acknowledged, Link failed", link.getName())); } // second message was not answered, report failure linkFailed(link); this.started = false; } return 0; } } // ////////////////// // Helper methods // // ////////////////// public static final int dpc(byte[] sif, int shift) { int dpc = (sif[0 + shift] & 0xff | ((sif[1 + shift] & 0x3f) << 8)); return dpc; } public static final int opc(byte[] sif, int shift) { int opc = ((sif[1 + shift] & 0xC0) >> 6) | ((sif[2 + shift] & 0xff) << 2) | ((sif[3 + shift] & 0x0f) << 10); return opc; } public static final int sls(byte[] sif, int shift) { int sls = (sif[3 + shift] & 0xf0) >>> 4; return sls; } public static final int si(byte[] data) { int serviceIndicator = data[0] & 0x0f; return serviceIndicator; } public static final int ssi(byte[] data) { // see Q.704.14.2 int subserviceIndicator = (data[0] >> 4) & 0x0F; return subserviceIndicator; } public static void writeRoutingLabel(byte[] data, int si, int ssi, int sls, int dpc, int opc) { // see Q.704.14.2 writeRoutingLabel(0, data, si, ssi, sls, dpc, opc); } public static void writeRoutingLabel(int shift, byte[] data, int si, int ssi, int sls, int dpc, int opc) { // see Q.704.14.2 data[0 + shift] = (byte) (((ssi & 0x0F) << 4) | (si & 0x0F)); data[1 + shift] = (byte) dpc; data[2 + shift] = (byte) (((dpc >> 8) & 0x3F) | ((opc & 0x03) << 6)); data[3 + shift] = (byte) (opc >> 2); data[4 + shift] = (byte) (((opc >> 10) & 0x0F) | ((sls & 0x0F) << 4)); } }