All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.restcomm.protocols.ss7.mtp.Mtp2 Maven / Gradle / Ivy

There is a newer version: 8.0.0-179
Show newest version
/*
 * 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.restcomm.protocols.ss7.mtp;

import java.io.IOException;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.restcomm.protocols.ss7.mtp.Mtp3.SLTMTest;
import org.restcomm.protocols.ss7.scheduler.Scheduler;
import org.restcomm.protocols.ss7.scheduler.Task;

/**
 *
 * @author kulikov
 * @author baranowb
 */
public class Mtp2 {

    static final int MTP2_OUT_OF_SERVICE = 0;
    /**
     * Initial state of IAC phase, we send "O" here. "E","O","N" have never been received.
     */
    static final int MTP2_NOT_ALIGNED = 1;
    /**
     * Second state of IAC, we received one of: E,O,N. Depending on state we send E or N.
     */
    static final int MTP2_ALIGNED = 2;
    /**
     * Third state, entered from ALIGNED on receival of N or E
     */
    static final int MTP2_PROVING = 3;
    /**
     * Etnered on certain condition when T4 fires.
     */
    static final int MTP2_ALIGNED_READY = 4;
    /**
     * In service state, entered from ALIGNED_READY on FISU/MSU
     */
    static final int MTP2_INSERVICE = 5;
    /**
     * Timers
     */
    private static final int T2_TIMEOUT = 50;
    private static final int T3_TIMEOUT = 10;
    private static final int T4_TIMEOUT_NORMAL = 82;
    private static final int T4_TIMEOUT_EMERGENCY = 5; // not a static, since it can change.
    private int T4_TIMEOUT = T4_TIMEOUT_NORMAL;
    private int T7_TIMEOUT = 20; // see Q704 - after alignemtn failure we need to wait before we retry
    // 800-1500.
    private static final int T17_TIMEOUT = 15;
    private static final int[] fcstab = new int[] { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48,
            0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7,
            0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726,
            0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291,
            0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204,
            0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a,
            0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9,
            0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c,
            0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf,
            0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e,
            0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad,
            0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618,
            0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b,
            0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5,
            0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60,
            0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7,
            0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46,
            0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9,
            0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 };
    /**
     * status indicator of out of alignment (SIO). This condition occurs when a signal unit is received that has a ones-density
     * violation (the data field simulated a flag) or the SIF has exceeded its maximum capacity of 272 octets. The SIO is sent
     * when a link has failed and the alignment procedure is initiated.
     */
    private static final int FRAME_STATUS_INDICATION_O = 0;
    /**
     * Link status indicator of normal (SIN) or emergency (SIE) indicates that the transmitting signaling point has initiated
     * the alignment procedure. The link is made unavailable for MSUs, and only FISUs are transmitted to the affected signaling
     * point until a proving period has been passed. After successful completion of a proving period, MSUs can be transmitted
     * over the link to the affected signaling point.
     */
    private static final int FRAME_STATUS_INDICATION_N = 1;
    private static final int FRAME_STATUS_INDICATION_E = 2;
    /**
     * An LSSU status indicator of out of service (SIOS) indicates that the sending signaling point cannot receive or transmit
     * any MSUs for reasons other than a processor outage. On receipt of an SIOS, the receiving signaling point stops the
     * transmission of MSUs and begins transmitting FISUs. The SIOS is also sent at the beginning of the alignment procedure.
     */
    private static final int FRAME_STATUS_INDICATION_OS = 3;
    /**
     * status indicator of processor outage (SIPO) indicates that the transmitting signaling point cannot communicate with
     * levels 3 and 4.
     */
    private static final int FRAME_STATUS_INDICATION_PO = 4;
    private static final int FRAME_STATUS_INDICATION_B = 5;
    private static final int FRAME_FISU = 6;
    /** matches frame code to frame name,FRAME_NAMES[0] gives string name of frame with code 0 */
    private static final String[] FRAME_NAMES;

    static {
        FRAME_NAMES = new String[] { "SIO", "SIN", "SIE", "SIOS", "SIPO", "SIPB", "FISU" };

    }
    public static final int AERM_THRESHOLD_NORMAL = 4;
    public static final int AERM_THRESHOLD_EMERGENCY = 1; // /////////////////////////////////////////////
    // States for MTP2, for now we merge IAC,LSC //
    // /////////////////////////////////////////////
    // Some static vals used for debug.
    private static final String[] STATE_NAMES;

    static {
        STATE_NAMES = new String[] { "OUT_OF_SERVICE", "NOT_ALIGNED", "ALIGNED", "PROVING", "ALIGNED_READY", "IN_SERVICE" };

    }
    // //////////////////
    // MTP2/1 resoruces //
    // //////////////////
    protected SLTMTest sltmTest;
    /** MTP1 layer reference. */
    private Mtp1 channel;
    private int ioBufferSize;
    // ///////////////////
    // MTP2 stuff only //
    // ///////////////////

    // be careful using this, we do not sync for now.
    private int state;
    /** MTP3 Layer reference */
    protected Mtp3 mtp3;
    private volatile boolean started;
    /** HDLC components */
    private FastHDLC hdlc = new FastHDLC();
    private HdlcState rxState = new HdlcState();
    private HdlcState txState = new HdlcState();

    // Buffers used for IO
    private byte[] txBuffer;
    private byte[] rxBuffer;

    /**
     * Buffer to store incoming frame.
     */
    private Mtp2Buffer rxFrame = new Mtp2Buffer();
    /**
     * Buffer to store frame to send, this ref will be set when next frame is scheduled, no copy&paste
     */
    private Mtp2Buffer txFrame;
    /**
     * frame used to create LSSU/FISU
     */
    private Mtp2Buffer stateFrame = new Mtp2Buffer();
    /**
     * frame used to create NEXT LSSU, it is used in case when link transits from NOT_ALIGNED to ALIGNED state
     */
    private Mtp2Buffer nextStateFrame = new Mtp2Buffer();
    /**
     * Indicates that there is something to send in {@link #nextStateFrame}.
     */
    private boolean pendingLSSU = false;

    /**
     * Last write timestamp, it is required for case when we must start sending but nothing comes on wire.
     */
    // private long lastWriteStamp = 0;
    // Used only for LSSU, in some case we need to schedule LSSU frame, before
    // last is processed,
    // private TxQueue txQueue = new TxQueue();
    private int doCRC = 0;
    private int rxCRC = 0xffff;
    private int txCRC = 0xffff;
    // //////////////////////
    // SEQUENCING AND RTR //
    // //////////////////////
    /**
     * Holds byte[] sent on link, indexed by FSN, this is the place to get data from when we send and retransmission is
     * requested. FIXME: add this-> ACKED buffers will have len == 0(isFree() == true);
     */
    private Mtp2Buffer[] transmissionBuffer = new Mtp2Buffer[128];
    private static final int _OFF_RTR = -1;
    /**
     * Determines posistion in transmissionBuffer. If != _OFF_RTR, it points next frame to be put into retransmission.
     * Retransmission will occur until ticks wont get to point where buffer.isFree() == true. It also point to next MSU if
     * scheduledby MTP3. This number is set as FSN.
     */
    private int retransmissionFSN = _OFF_RTR;
    /**
     * This is last ACKED FSN(meaning this is BSN received in SU comming from other peer.
     */
    private int retransmissionFSN_LastAcked = 0;
    /**
     * This is FSN we used as last for send SU. If we spin many times and it equals retransmissionFSN_LastAcked, it means we
     * have queue full, and have to discard SU
     */
    private int retransmissionFSN_LastSent = 0;
    private int sendFIB;
    private int sendBSN;
    private int sendBIB;
    /**
     * if bsnErrors > 2, link fails. See Q.703 section 5.3.[1,2]
     */
    private int bsnErrors = 0;
    protected String name; // AERM variables. we use eCounter for AERM errors
    private static final int PROVING_ATTEMPTS_THRESHOLD = 5;
    private int aermThreshold;
    private boolean aermEnabled;
    private boolean emergency = false;
    private int provingAttempts;
    private boolean futureProving;
    private int nCount;
    private int dCount;
    private int eCount;
    /**
     * SLS, range is 0-15
     */
    protected int sls = -1;
    /**
     * Subservice field of mtp msg. See Q.704.14.2.2
     */

    protected Mtp2Listener mtp2Listener = null;

    private static final Logger ROOT_LOGGER = Logger.getLogger(Mtp2.class);
    private final Logger logger; // actual logger.

    private Scheduler scheduler;

    public Mtp2(String name, Mtp1 channel, Scheduler scheduler) {
        this.name = name;
        this.logger = ROOT_LOGGER.getLogger(name);
        this.channel = channel;
        channel.setLink(this);
        this.sls = channel.getCode();
        this.scheduler = scheduler;

        if (scheduler != null) {
            t2Action = new T2Action(scheduler);
            t3Action = new T3Action(scheduler);
            t4Action = new T4Action(scheduler);
            t7Action = new T7Action(scheduler);
            t17Action = new T17Action(scheduler);
        }

        // init HDLC
        hdlc.fasthdlc_precalc();
        hdlc.fasthdlc_init(rxState);
        hdlc.fasthdlc_init(txState);

        // init error rate monitor
        aermThreshold = AERM_THRESHOLD_NORMAL;

        // init buffer
        for (int i = 0; i < this.transmissionBuffer.length; i++) {
            this.transmissionBuffer[i] = new Mtp2Buffer();
        }
    }

    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;

        if (t2Action == null && scheduler != null) {
            t2Action = new T2Action(scheduler);
            t3Action = new T3Action(scheduler);
            t4Action = new T4Action(scheduler);
            t7Action = new T7Action(scheduler);
            t17Action = new T17Action(scheduler);
        }
    }

    public Mtp2Listener getMtp2Listener() {
        return mtp2Listener;
    }

    public void setMtp2Listener(Mtp2Listener mtp2Listener) {
        this.mtp2Listener = mtp2Listener;
    }

    public boolean isEmergency() {
        return emergency;
    }

    public void setEmergency(boolean emergency) {
        this.emergency = emergency;
    }

    public int getState() {

        return this.state;
    }

    /**
     * Assigns layer 1 implementation.
     *
     * @param layer1 the implementation of MTP1 layer.
     */
    public void setLayer1(Mtp1 layer1) {
        this.channel = layer1;
    }

    public Mtp1 getLayer1() {
        return channel;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.restcomm.protocols.ss7.mtp.Mtp2#getName()
     */
    public String getName() {
        return this.name;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.restcomm.protocols.ss7.mtp.Mtp2#getSls()
     */
    public int getSls() {
        return this.sls;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.restcomm.protocols.ss7.mtp.Mtp2#setName(java.lang.String)
     */
    public void setName(String name) {
        this.name = name;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.restcomm.protocols.ss7.mtp.Mtp2#setSls(int)
     */
    public void setSls(int sls) {
        this.sls = sls;

    }

    public void start() throws IOException {
        if (channel == null) {
            throw new IllegalStateException("Layer1 is not set in Layer2!");
        }
        if (mtp3 == null) {
            throw new IllegalStateException("Layer3 is not set in Layer2!");
        }
        if (started) {
            throw new IllegalStateException("Link already running");
        }
        // init buffers
        this.ioBufferSize = this.channel.getIOBufferSize();
        this.rxBuffer = new byte[this.ioBufferSize];
        this.txBuffer = new byte[this.ioBufferSize];
        // open channel and notify L3
        channel.open();
        mtp3.registerLink(this);
        started = true;

        this.reset();

        if (logger.isInfoEnabled()) {
            logger.info("Is out of service now. Starting.");
        }
        this.txFrame = stateFrame;
        // state = Mtp2.MTP2_OUT_OF_SERVICE;
        setState(MTP2_OUT_OF_SERVICE);
        // send Out of service indicator when link starts.
        queueLSSU(Mtp2.FRAME_STATUS_INDICATION_OS);
        processTx(this.ioBufferSize);
        startInitialAlignment();
    }

    public void stop() {
        if (logger.isInfoEnabled()) {
            logger.info("Stopping link.");
        }
        mtp3.unregisterLink(this);
        started = false;
        reset();
        this.channel.close();
    }

    // not used
    private void startInitialAlignment() {
        startInitialAlignment(true);
    }

    // should not be used
    protected void startInitialAlignment(boolean resetTxOffset) {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("(%s) Starting initial alignment", name));
        }

        // Comment from Oleg: this is done initialy to setup correct spot in tx
        // buffer: dunno, I just believe, for now.
        if (resetTxOffset) {
            // txOffset = 3;
            this.txFrame.offset = 3; // I really dont get this shift.
        }

        this.reset();

        // switch state

        // this.state = MTP2_NOT_ALIGNED;
        this.setState(MTP2_NOT_ALIGNED);

        // starting T2 timer
        start_T2();
    }

    private void setState(int newState) {
        if (this.state == newState) {
            return;
        }

        if (logger.isInfoEnabled()) {
            // iirc it should be info
            logger.info("State changed in link. " + STATE_NAMES[this.state] + " --> " + STATE_NAMES[newState]);
        }
        this.state = newState;
    }

    private void reset() {
        // reset.
        this.nCount = 0;
        this.eCount = 0;
        this.dCount = 0;

        // set proper values of FIBOS, FISSIES And other...
        this.sendFIB = 1;
        this.sendBSN = 0x7F;
        this.sendBIB = 1;
        this.retransmissionFSN = _OFF_RTR;
        this.retransmissionFSN_LastAcked = 0x7F;
        this.retransmissionFSN_LastSent = 0x7F;
    }

    public void fail() {
        cleanupTimers();
        this.reset();

        this.setState(MTP2_OUT_OF_SERVICE);

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("(%s) Link Out of service", name));
        }

        start_T17();
    }

    public boolean send(byte[] msg, int len) {
        // FIXME: this operation actually could be split into two....
        if (this.state != MTP2_INSERVICE) {
            return false;
        }
        // Here we will queue MSU if there is place in transmission buffer
        int possibleFSN = NEXT_FSN(this.retransmissionFSN_LastSent);
        // check if transmission buffer is full
        if (possibleFSN == this.retransmissionFSN_LastAcked) {
            // This means buffer is full;
            return false;
        }
        // FSN and all that will be set before puting this into txFrame buffer.
        this.transmissionBuffer[possibleFSN].frame[0] = 0;
        this.transmissionBuffer[possibleFSN].frame[1] = 0;
        // LI: see Q.703 2.3.3, FIXME: add check for msg.length <3 ?
        this.transmissionBuffer[possibleFSN].frame[2] = (byte) (len >= 63 ? 63 : len);

        System.arraycopy(msg, 0, this.transmissionBuffer[possibleFSN].frame, 3, len);
        // 3 == FSN(1) + BSN(1) + LI(1)
        this.transmissionBuffer[possibleFSN].len = 3 + len;

        this.retransmissionFSN_LastSent = possibleFSN;
        if (this.retransmissionFSN == _OFF_RTR) {
            this.retransmissionFSN = possibleFSN;
        }
        this.start_T7();
        // txQueue.offer(frame);
        return true;
    }

    private void queueLSSU(int indicator) {
        // if (this.txLen != txOffset) {
        if (this.txFrame != null && this.txFrame.len != this.txFrame.offset) {
            fillLSSUBuffer(this.nextStateFrame, indicator);
            this.pendingLSSU = true; // should we check on entering here?
        } else {
            this.txFrame = this.stateFrame; // set proper frame
            fillLSSUBuffer(this.txFrame, indicator);
        }
    }

    private void fillLSSUBuffer(Mtp2Buffer b, int indicator) {
        b.len = 4;
        b.offset = 0;
        b.frame[0] = (byte) (this.sendBSN | (this.sendBIB << 7));
        b.frame[1] = (byte) (this.retransmissionFSN_LastSent | (this.sendFIB << 7));
        b.frame[2] = 1;
        b.frame[3] = (byte) indicator;

    }

    private void queueFISU() {

        this.txFrame.len = 3;
        this.txFrame = this.stateFrame;
        this.txFrame.frame[0] = (byte) (this.sendBSN | (this.sendBIB << 7));
        this.txFrame.frame[1] = (byte) (this.retransmissionFSN_LastSent | (this.sendFIB << 7));
        this.txFrame.frame[2] = 0;
        this.txFrame.offset = 0;
    }

    private void queueNextFrame() {
        if (this.state != MTP2_INSERVICE && this.pendingLSSU) {
            this.txFrame = nextStateFrame;
            this.pendingLSSU = false;

        } else {
            switch (state) {
                case MTP2_OUT_OF_SERVICE:
                    // send SIOS ?
                    queueLSSU(FRAME_STATUS_INDICATION_OS);
                    break;
                case MTP2_NOT_ALIGNED:
                    queueLSSU(FRAME_STATUS_INDICATION_O);
                    break;
                case MTP2_PROVING:
                case MTP2_ALIGNED:
                    // which way is correct?
                    if (emergency) {
                        queueLSSU(FRAME_STATUS_INDICATION_E);
                    } else {
                        queueLSSU(FRAME_STATUS_INDICATION_N);
                    }
                    break;
                default:

                    // in service, we need to check buffer, otherwise its RTR
                    if (this.retransmissionFSN != _OFF_RTR) {
                        Mtp2Buffer buffer = this.transmissionBuffer[this.retransmissionFSN];
                        // we shoudl use getters, but its faster with "."
                        this.txFrame = buffer;
                        this.txFrame.offset = 0;
                        this.txFrame.frame[0] = (byte) (this.sendBSN | (this.sendBIB << 7));
                        this.txFrame.frame[1] = (byte) (this.retransmissionFSN | (this.sendFIB << 7));

                        if (this.retransmissionFSN == this.retransmissionFSN_LastSent) {
                            this.retransmissionFSN = _OFF_RTR;
                        } else {
                            // set up next :)
                            this.retransmissionFSN = NEXT_FSN(this.retransmissionFSN);
                        }

                        return;
                    }
                    // else queue FISU
                    queueFISU();
            }
        }
    }

    public static final int PPP_FCS(int fcs, int c) {
        return ((fcs) >> 8) ^ fcstab[((fcs) ^ (c)) & 0xff];
    }

    private void processLssu() {
        // Q703 Figure 4
        int type = rxFrame.frame[3] & 0x07;

        switch (this.state) {
            case MTP2_NOT_ALIGNED:
                // here we wait for O/N/E
                switch (type) {
                    case FRAME_STATUS_INDICATION_O:
                    case FRAME_STATUS_INDICATION_N:
                        stop_T2();
                        if (emergency) {
                            this.T4_TIMEOUT = T4_TIMEOUT_EMERGENCY;
                            queueLSSU(FRAME_STATUS_INDICATION_E);
                        } else {
                            this.T4_TIMEOUT = T4_TIMEOUT_NORMAL;
                            queueLSSU(FRAME_STATUS_INDICATION_N);
                        }
                        start_T3();

                        this.setState(MTP2_ALIGNED);
                        break;
                    case FRAME_STATUS_INDICATION_E:
                        // 1. stop t2
                        stop_T2();
                        // 2. set T4 time here we set always to emergency, only LSSU,
                        // differs depending on flag.
                        this.T4_TIMEOUT = T4_TIMEOUT_EMERGENCY;
                        // 3. queue response
                        if (emergency) {
                            queueLSSU(FRAME_STATUS_INDICATION_E);
                        } else {
                            queueLSSU(FRAME_STATUS_INDICATION_N);
                        }
                        // 4. start T3
                        start_T3();
                        // 5. set state

                        this.setState(MTP2_ALIGNED);
                        break;
                    default:

                }
                // BREAK For NOT_ALIGNED state
                break;

            case MTP2_ALIGNED:

                switch (type) {
                // Here we ignore "O"
                    case FRAME_STATUS_INDICATION_E:
                        // 1. set T4 time.
                        this.T4_TIMEOUT = Mtp2.T4_TIMEOUT_EMERGENCY;
                    case FRAME_STATUS_INDICATION_N:
                        // 1. stop T3
                        stop_T3();
                        // 2. determine threashold for AERM.
                        if (this.T4_TIMEOUT == Mtp2.T4_TIMEOUT_EMERGENCY) {
                            this.aermThreshold = Mtp2.AERM_THRESHOLD_EMERGENCY;
                        }
                        // 3. start AERM, but we do start, it works alwys? this should
                        // reset CP also?
                        startAERM();
                        // 4. start T4;
                        start_T4();
                        // 5. cp = 0;
                        this.provingAttempts = 0;
                        // 6. cancel further proving.
                        this.futureProving = false;
                        // 7. set state

                        this.setState(MTP2_PROVING);
                        break;
                    case FRAME_STATUS_INDICATION_OS:
                        // we should invoke ALI first, but we cancel timer
                        // 1. stop T3
                        stop_T3();
                        // 2. ALI not possible, this will switch to OUT_OF_SERVICE(in
                        // IAC its IDLE.) state, possibly fire OS, set T17.
                        alignmentNotPossible("Receievd SIOS in state ALIGNED");
                        // 3. cancel E
                        this.emergency = false;

                        break;
                    default:

                }

                // BREAK for ALIGNED state.
                break;
            case MTP2_PROVING:
                switch (type) {
                    case FRAME_STATUS_INDICATION_O:
                        // 1. stop T4
                        stop_T4();
                        // 2. stop AERM, hmm
                        stopAERM();
                        // 3. start T3
                        start_T3();
                        // 4. swithc state

                        this.setState(MTP2_ALIGNED);
                        break;
                    case FRAME_STATUS_INDICATION_E:
                        // NOTE: this one is not covered in Olegs src.
                        // 1. determine actions
                        if (this.T4_TIMEOUT == Mtp2.T4_TIMEOUT_EMERGENCY) {
                            // do nothing
                            return;
                        }

                        // 2. stop T4
                        stop_T4();
                        // 3. stop AERM,
                        stopAERM();
                        // 4. set AERM T
                        this.aermThreshold = Mtp2.AERM_THRESHOLD_EMERGENCY;
                        // 5. start AERM
                        startAERM();
                        // 6. Cancel Further Proving
                        this.futureProving = false;
                        // 7. start T4
                        start_T4();
                        // 8. state is still proving.

                        break;
                    case FRAME_STATUS_INDICATION_OS:
                        // 1. stop T4
                        stop_T4();
                        // 2. action;
                        // specs shows : == Aligment Complete ....
                        alignmentNotPossible("Received SIOS in state PROVING");
                        // 3. stop AERM
                        stopAERM();
                        // 4. cancel E
                        this.emergency = false;
                        // 5. should move to state IDLE, alignmentNotPossible will shift
                        // state.
                        break;
                    default:

                }
                // BREAK for PROVING state
                break;
            case MTP2_ALIGNED_READY:

                switch (type) {
                    case FRAME_STATUS_INDICATION_O:

                        // link failed.

                        // FIXME: add more state here.
                        alignmentNotPossible("Received SIO in state ALIGNED_READY");
                        break;
                    case FRAME_STATUS_INDICATION_OS:
                        // link failed, pass to MTP3?
                        // mpt3.linkFailure();
                        // FIXME: add more
                        alignmentNotPossible("Received SIOS in state ALIGNED_READY");
                    default:

                }
                // ... dont forget to break!!!
                // this caused link to go down ;[
                break;
            case MTP2_INSERVICE:

                switch (type) {
                    case FRAME_STATUS_INDICATION_O:
                    case FRAME_STATUS_INDICATION_E:
                    case FRAME_STATUS_INDICATION_N:

                        // link failed, pass to MTP3?
                        // mpt3.linkFailure();
                        // FIXME: add more
                        alignmentNotPossible("Received " + FRAME_NAMES[type] + " in state IN_SERVICE");
                        break;
                    case FRAME_STATUS_INDICATION_OS:
                        // link failed, pass to MTP3?
                        // mpt3.linkFailure();
                        // FIXME: add more
                        alignmentNotPossible("Received SIOS in state IN_SERVICE");
                    default:

                }
                // BREAK for ALIGNED_READY
                break;
            default:

        }

    }

    private void processMSU(int len) {
        if (this.mtp3 != null) {
            mtp3.onMessage(rxFrame, this);
        }
    }

    private void processFrame() {

        int bsn = rxFrame.frame[0] & 0x7F;
        int bib = (rxFrame.frame[0] >> 7) & 0x01;
        int fsn = rxFrame.frame[1] & 0x7F;
        int fib = (rxFrame.frame[1] >> 7) & 0x01;

        int li = rxFrame.frame[2] & 0x3f;

        // Why it was 5?
        // if (li + 3 > rxLen) {
        if (li + 3 > rxFrame.len) {

            return;
        }

        if (li == 1 || li == 2) {

            // LSSU does not count for IAM check for SU
            processLssu();
            // LSSU does not go into BSN/FSN check, its always static for this
            // part.
            // and if we get here from IN_SERVICE, everything is reset.

            return;
        }

        if (this.state != MTP2_INSERVICE) {
            switch (this.state) {
                case MTP2_PROVING:

                    // Decision;
                    if (futureProving) {
                        // 1. start AERM, maybe restart?
                        startAERM();
                        // 2. cancel FP
                        futureProving = false;
                        // 3. start T4
                        start_T4();
                        // 4. state is still proving

                    } else {
                        // 1. do nothing, stay in PROVING state,
                    }

                    // FIXME: return here?
                    break;
                case MTP2_ALIGNED_READY:

                    this.eCount = 0;
                    this.dCount = 0;
                    this.stop_T7();

                    this.sendFIB = bib;
                    this.sendBSN = fsn;
                    this.sendBIB = fib;
                    this.retransmissionFSN_LastAcked = bsn;

                    if (logger.isDebugEnabled()) {
                        logger.debug(String.format("(%s) MTP now IN_SERVICE, Notifing layer 3", name));
                    }
                    this.setState(MTP2_INSERVICE);
                    mtp3.linkInService(this);
                    break;
            }
        }
        // Q.703 section 5.3.1 and 5.3.2, paras like:
        // If any two backward sequence number values in three consecutively
        // received message signal units or
        // fill-in signal units are not the same as the previous one or any of
        // the forward sequence number values
        // of the signal units in the retransmission buffer at the time that
        // they are received, then level 3 is
        // informed that the link is faulty.

        // This means we have to check BSN if it falls into space available for
        // rtr
        // its a bit complicated since we have "ring" buffer. where indexes are
        // reused.

        // CASE_I, buffer did not flip.
        if ((this.retransmissionFSN_LastAcked <= this.retransmissionFSN_LastSent && (bsn < this.retransmissionFSN_LastAcked || bsn > this.retransmissionFSN_LastSent))
                ||
                // CASE_II, buffer flipped, lastSent is less than ack, since it
                // starts from "0"
                (this.retransmissionFSN_LastAcked > this.retransmissionFSN_LastSent && (bsn < this.retransmissionFSN_LastAcked && bsn > this.retransmissionFSN_LastSent))) {
            this.bsnErrors++;
            if (this.bsnErrors > 2) {
                this.bsnErrors = 0;
                this.mtp3.linkFailed(this);
                alignmentBroken("Broken BSN constrains: fsn_lasAcked = " + this.retransmissionFSN_LastAcked
                        + ", fsn_LastSent = " + this.retransmissionFSN_LastSent + ", bsn = " + bsn);
            }

            return;
        }

        this.bsnErrors = 0;

        // Q.703, Section 5.3.1, T7. it is weird, its in positive section
        // but it says "ACK", any?

        if (bib == this.sendFIB) {

            if (bsn != this.retransmissionFSN_LastAcked) {

                this.stop_T7();
                this.retransmissionFSN_LastAcked = bsn;

                if (this.retransmissionFSN_LastAcked != this.retransmissionFSN_LastSent) {

                    // there is something we can retransmit, we wait for ACK
                    this.start_T7();
                }
            }
        } else {

            // negative, start rtr
            this.sendFIB = bib;
            if (bsn == this.retransmissionFSN_LastSent) {

                // there is nothing, should we error here?
                this.retransmissionFSN = _OFF_RTR;
            } else {

                this.retransmissionFSN = NEXT_FSN(bsn);
            }
        }

        if (li == 0) {

            if (fsn != this.sendBSN) {

                // something is not correctm link lost msg?
                // Q.703 section 5.2.2.a.ii
                if (fib == this.sendBIB) {

                    // flip, to make negative ACK, ask for rtr.
                    this.sendBIB = NEXT_INDICATOR(this.sendBIB);
                }
            }

            // thats it for FISU

        } else {

            if (fsn == this.sendBSN) {

                // Q.703 section 5.2.2.c.i
                // we already got this once.
                return;

            } else if (fsn == NEXT_FSN(this.sendBSN)) {

                // Q.703 section 5.2.2.c.ii
                if (fib == this.sendBIB) {

                    // positive ACK, lets send it.
                    this.sendBSN = fsn;
                } else {

                    // drop frame? since it negative?
                    return;
                }
            } else {

                // Q.703 section 5.2.2.c.iii
                // lost some frame, make negative
                if (fib == this.sendBIB) {

                    // flip, to make negative ACK, ask for rtr.
                    this.sendBIB = NEXT_INDICATOR(this.sendBIB);
                }
                return;
            }
            processMSU(li);

        }

    }

    /**
     * Handles received data.
     *
     * @param buff the buffer which conatins received data.
     * @param len the number of received bytes.
     */
    private void processRx(byte[] buff, int len) {
        int i = 0;
        // start HDLC alg
        while (i < len) {
            while (rxState.bits <= 24 && i < len) {
                int b = buff[i++] & 0xff;
                hdlc.fasthdlc_rx_load_nocheck(rxState, b);
                if (rxState.state == 0) {
                    // octet counting mode
                    nCount = (nCount + 1) % 16;
                    if (nCount == 0) {
                        countError("on receive");
                    }
                }
            }

            int res = hdlc.fasthdlc_rx_run(rxState);

            switch (res) {
                case FastHDLC.RETURN_COMPLETE_FLAG:
                    // frame received and we count it
                    countFrame();

                    // checking length and CRC of the received frame
                    if (rxFrame.len == 0) {
                    } else if (rxFrame.len < 5) {
                        // frame must be at least 5 bytes in length
                        countError("hdlc error, frame LI<5");
                    } else if (rxCRC == 0xF0B8) {
                        // good frame received
                        processFrame();
                    } else {
                        countError("hdlc complete, wrong terms.");
                    }
                    rxFrame.len = 0;
                    rxCRC = 0xffff;
                    break;
                case FastHDLC.RETURN_DISCARD_FLAG:
                    // looking for next flag
                    rxCRC = 0xffff;
                    rxFrame.len = 0;
                    // eCount = 0;
                    countFrame();
                    // "on receive, hdlc discard"
                    countError("hdlc discard.");
                    break;
                case FastHDLC.RETURN_EMPTY_FLAG:
                    rxFrame.len = 0;
                    break;
                default:
                    if (rxFrame.len > 279) {
                        rxState.state = 0;
                        rxFrame.len = 0;
                        rxCRC = 0xffff;
                        eCount = 0;
                        countFrame();
                        countError("Overlong MTP frame, entering octet mode on link '" + name + "'");
                    } else {
                        rxFrame.frame[rxFrame.len++] = (byte) res;
                        rxCRC = PPP_FCS(rxCRC, res & 0xff);
                    }
            }
        }
    }

    private void processTx(int bytesRead) throws IOException {
        for (int i = 0; i < bytesRead && i < this.ioBufferSize; i++) {
            if (txState.bits < 8) {
                // need more bits
                if (doCRC == 0 && txFrame.offset < txFrame.len) {
                    int data = txFrame.frame[txFrame.offset++] & 0xff;
                    hdlc.fasthdlc_tx_load(txState, data);
                    txCRC = PPP_FCS(txCRC, data);
                    if (txFrame.offset == txFrame.len) {
                        doCRC = 1;
                        txCRC ^= 0xffff;
                    }
                } else if (doCRC == 1) {
                    hdlc.fasthdlc_tx_load_nocheck(txState, (txCRC) & 0xff);
                    doCRC = 2;
                } else if (doCRC == 2) {
                    hdlc.fasthdlc_tx_load_nocheck(txState, (txCRC >> 8) & 0xff);
                    doCRC = 0;
                } else {
                    // nextFrame();

                    queueNextFrame();

                    txFrame.offset = 0;
                    txCRC = 0xffff;
                    hdlc.fasthdlc_tx_frame_nocheck(txState);
                }
            }
            // txBuffer.put(i, (byte) hdlc.fasthdlc_tx_run_nocheck(txState));
            txBuffer[i] = (byte) hdlc.fasthdlc_tx_run_nocheck(txState);
        }
    }

    public void doRead() {
        if (started) {
            try {
                int bytesRead = channel.read(rxBuffer);
                if (bytesRead > 0) {
                    processRx(rxBuffer, bytesRead);
                }
            } catch (Exception e) {
                if (logger.isEnabledFor(Level.ERROR)) {
                    logger.error(String.format("(%s) Can not read data from channel", name), e);
                }
                this.setState(MTP2_OUT_OF_SERVICE);
                mtp3.linkFailed(this);
            }
        }
    }

    public void doWrite() {
        if (!started) {
            return;
        }
        try {
            processTx(this.ioBufferSize);
            channel.write(txBuffer, this.ioBufferSize);
        } catch (Exception e) {
            if (logger.isEnabledFor(Level.ERROR)) {
                logger.error(String.format("(%s) Can not write data to channel", name), e);
            }
            this.setState(MTP2_OUT_OF_SERVICE);
            mtp3.linkFailed(this);
        }
    }

    // //////////////////////////
    // PRIVATE HELPER METHODS //
    // //////////////////////////
    private void stopAERM() {
        this.aermEnabled = false;

    }

    private void startAERM() {
        this.eCount = 0;
        // we reset, for octet counting mode.
        this.nCount = 0;
        this.aermEnabled = true;

    }

    private void alignmentNotPossible(String cause) {
        this.cleanupTimers();
        this.reset();
        if (this.state == MTP2_INSERVICE) {
            if (this.mtp3 != null) {
                this.mtp3.linkFailed(this);
            }
        }
        this.setState(MTP2_OUT_OF_SERVICE);
        // NOTE: buffer is flushed on start alignment.
        start_T17();
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("(%s) Alignment not possible, initiating T17 for restart. Cause: %s", name, cause));
        }
    }

    private void alignmentBroken(String cause) {
        // FIXME: check if we can merge with above method.
        this.cleanupTimers();
        this.reset();
        if (this.state == MTP2_INSERVICE) {
            if (this.mtp3 != null) {
                this.mtp3.linkFailed(this);
            }
        }

        this.setState(MTP2_OUT_OF_SERVICE);
        // NOTE: buffer is flushed on start alignment.
        start_T17();

        if (logger.isDebugEnabled()) {
            logger.debug("Alignment broken, initiating T17 for restart. Cause: " + cause);
        }
    }

    private void cleanupTimers() {
        this.stop_T2();
        this.stop_T3();
        this.stop_T4();
        this.stop_T7();
        this.stop_T17();

    }

    private void alignmentComplete() {

        stop_T2();
        stop_T3();
        stop_T4();

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("(%s) Aligned ready", name));
        }
        this.setState(MTP2_ALIGNED_READY);
        // now we wait for proper data
    }

    private void countError(String info) {
        eCount++;

        // logger.info("ERROR:" + info);
        switch (state) {
            case MTP2_ALIGNED_READY:
            case MTP2_INSERVICE:
                if (eCount >= 64) {
                    if (this.mtp3 != null) {
                        mtp3.linkFailed(this);
                    }

                    this.setState(MTP2_OUT_OF_SERVICE);
                }
                break;
            case MTP2_PROVING:
                if (this.aermEnabled) {
                    if (eCount >= aermThreshold) {
                        // start T17 ? check for alignemtn count;
                        // see Q.703 p.55
                        this.provingAttempts++;
                        if (this.provingAttempts < PROVING_ATTEMPTS_THRESHOLD) {
                            this.futureProving = true;

                            if (this.logger.isEnabledFor(Level.WARN)) {
                                // FIXME: should this remain warn ?
                                logger.warn("Exceeded AERM threshold[ " + aermThreshold + " ] errors[ " + eCount
                                        + " ], proving attempts[ " + provingAttempts + " ], continue...");
                            }
                            return;
                        }

                        // 1. Alignment not possible ---> ?
                        alignmentNotPossible("Exceeded AERM threshold[" + aermThreshold + "] errors[" + eCount
                                + "], proving attempts[" + provingAttempts + "]");

                        // 2. stop T3
                        stop_T3();
                        // 3. cancel E
                        this.emergency = false;
                        // 4. stop AERM
                        stopAERM();
                        // 5. set state, should be done in #1.

                    }
                }
                break;
        }
    }

    /**
     * Increment number of received frames decrement error monitor countor for each 256 good frames.
     *
     */
    private void countFrame() {
        if (state == MTP2_ALIGNED_READY || state == MTP2_INSERVICE) {
            dCount = (dCount + 1) % 256;
            // decrement error countor for each 256 frames
            if (dCount == 0 && eCount > 0) {
                eCount--;
            }
        }
    }

    // //////////////////////
    // Timers and actions //
    // //////////////////////
    private T2Action t2Action;
    private T3Action t3Action;
    private T4Action t4Action;
    private T7Action t7Action;
    private T17Action t17Action;

    private class T2Action extends Task {
        private int ttl;

        public T2Action(Scheduler scheduler) {
            super(scheduler);
        }

        public int getQueueNumber() {
            return scheduler.HEARTBEAT_QUEUE;
        }

        public void start() {
            this.activate(true);
            ttl = T2_TIMEOUT;
            scheduler.submitHeatbeat(this);
        }

        public long perform() {
            if (ttl > 0) {
                ttl--;
                scheduler.submitHeatbeat(this);
                return 0;
            }

            // remove ref
            // T2.cancel(false);
            // T2 = null;
            stop_T2();
            int tmpState = state;
            if (tmpState == MTP2_NOT_ALIGNED) {
                // 1. ALI not possible
                alignmentNotPossible("T2 Expired.");
                // 2. cancel E
                emergency = false;
                if (logger.isEnabledFor(Level.WARN)) {
                    // FIXME: should this be debug ?
                    logger.warn("Timer T2 has expired, Alignment not possible. ");
                }
            } else {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("T2 fired in state[ " + STATE_NAMES[tmpState] + " ]");
                }
            }

            return 0;
        }
    }

    private class T3Action extends Task {
        private int ttl;

        public T3Action(Scheduler scheduler) {
            super(scheduler);
        }

        public int getQueueNumber() {
            return scheduler.HEARTBEAT_QUEUE;
        }

        public void start() {
            this.activate(true);
            ttl = T3_TIMEOUT;
            scheduler.submitHeatbeat(this);
        }

        public long perform() {
            if (ttl > 0) {
                ttl--;
                scheduler.submitHeatbeat(this);
                return 0;
            }

            // remove ref
            // T3.cancel(false);
            // T3 = null;
            stop_T3();
            int tmpState = state;
            if (tmpState == MTP2_ALIGNED) {
                // 1. ALI not possible
                alignmentNotPossible("T3 Expired.");
                // 2.cancel E
                emergency = false;
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("Timer T3 has expired, Alignment not possible. ");
                }
            } else {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("T3 fired in state[ " + STATE_NAMES[tmpState] + " ]");
                }
            }

            return 0;
        }
    }

    private class T4Action extends Task {
        private int ttl;

        public T4Action(Scheduler scheduler) {
            super(scheduler);
        }

        public int getQueueNumber() {
            return scheduler.HEARTBEAT_QUEUE;
        }

        public void start() {
            this.activate(true);
            ttl = T4_TIMEOUT;
            scheduler.submitHeatbeat(this);
        }

        public long perform() {
            if (ttl > 0) {
                ttl--;
                scheduler.submitHeatbeat(this);
                return 0;
            }

            // remove ref;
            // T4.cancel(false);
            // T4 = null;
            stop_T4();
            int tmpState = state;
            if (tmpState == MTP2_PROVING) {
                // decision
                if (futureProving) {
                    // 1. start AERM, maybe restart?
                    startAERM();
                    // 2. cancel FP
                    futureProving = false;
                    // 3. start myself :)
                    start_T4();
                    // 4. state is still proving

                } else {
                    alignmentComplete();
                }
            } else {

                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("T4 fired in state[ " + STATE_NAMES[tmpState] + " ]");
                }
            }

            return 0;
        }
    }

    private class T7Action extends Task {
        private int ttl;

        public T7Action(Scheduler scheduler) {
            super(scheduler);
        }

        public int getQueueNumber() {
            return scheduler.HEARTBEAT_QUEUE;
        }

        public void start() {
            this.activate(true);
            ttl = T7_TIMEOUT;
            scheduler.submitHeatbeat(this);
        }

        public long perform() {
            if (ttl > 0) {
                ttl--;
                scheduler.submitHeatbeat(this);
                return 0;
            }

            // FIXME: add this
            stop_T7();
            return 0;
        }
    }

    private class T17Action extends Task {
        private int ttl;

        public T17Action(Scheduler scheduler) {
            super(scheduler);
        }

        public int getQueueNumber() {
            return scheduler.HEARTBEAT_QUEUE;
        }

        public void start() {
            this.activate(true);
            ttl = T17_TIMEOUT;
            scheduler.submitHeatbeat(this);
        }

        // first alig works different.
        private boolean initial = true;

        public long perform() {
            if (ttl > 0) {
                ttl--;
                scheduler.submitHeatbeat(this);
                return 0;
            }

            logger.info(String.format("(%s) Restarting initial alignment", name));
            // there is somethin
            // T17.cancel(true);
            // T17 = null;
            stop_T17();
            if (state == MTP2_OUT_OF_SERVICE) {
                startInitialAlignment(initial);
            }
            if (initial) {
                initial = false;
            }

            return 0;
        }
    }

    private void start_T2() {
        this.stop_T2();
        t2Action.start();
    }

    private void stop_T2() {
        t2Action.cancel();
    }

    private void start_T3() {
        this.stop_T3();
        t3Action.start();
    }

    private void stop_T3() {
        t3Action.cancel();
    }

    private void start_T4() {
        this.stop_T4();
        t4Action.start();
    }

    private void stop_T4() {
        t4Action.cancel();
    }

    private void start_T7() {
        this.stop_T7();
        t7Action.start();
    }

    private void stop_T7() {
        t7Action.cancel();
    }

    // MTP3 actaully, its accessed by it, we just keep some state in mtp2
    public void start_T17() {
        this.stop_T17();
        t17Action.start();
    }

    public void stop_T17() {
        t17Action.cancel();
    }

    public boolean isT17() {
        return t17Action.isActive();
    }

    private static int NEXT_FSN(int x) {
        // kill FIB if present
        return ((x & 0x7F) + 1) % 128;
    }

    /**
     * neat way to flip indicator regardles of value.
     */
    private static int NEXT_INDICATOR(int x) {
        return (x + 1) % 2;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy