org.mobicents.protocols.sctp.netty.NettyAssociationImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.mobicents.protocols.sctp.netty.NettyAssociationImpl.java

Source

/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2014, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

package org.mobicents.protocols.sctp.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.sctp.SctpChannel;
import io.netty.channel.sctp.SctpChannelOption;
import io.netty.channel.sctp.SctpMessage;
import io.netty.channel.sctp.nio.NioSctpChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javolution.xml.XMLFormat;
import javolution.xml.stream.XMLStreamException;

import org.apache.log4j.Logger;
import org.mobicents.protocols.api.Association;
import org.mobicents.protocols.api.AssociationListener;
import org.mobicents.protocols.api.AssociationType;
import org.mobicents.protocols.api.IpChannelType;
import org.mobicents.protocols.api.ManagementEventListener;
import org.mobicents.protocols.api.PayloadData;

/**
 * @author <a href="mailto:amit.bhayani@telestax.com">Amit Bhayani</a>
 * 
 */
public class NettyAssociationImpl implements Association {

    protected static final Logger logger = Logger.getLogger(NettyAssociationImpl.class.getName());

    private static final String NAME = "name";
    private static final String SERVER_NAME = "serverName";
    private static final String HOST_ADDRESS = "hostAddress";
    private static final String HOST_PORT = "hostPort";

    private static final String PEER_ADDRESS = "peerAddress";
    private static final String PEER_PORT = "peerPort";

    private static final String ASSOCIATION_TYPE = "assoctype";
    private static final String IPCHANNEL_TYPE = "ipChannelType";
    private static final String EXTRA_HOST_ADDRESS = "extraHostAddress";
    private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize";

    private String hostAddress;
    private int hostPort;
    private String peerAddress;
    private int peerPort;
    private String serverName;
    private String name;
    private IpChannelType ipChannelType;
    private String[] extraHostAddresses;
    private NettyServerImpl server; // this is filled only for anonymous Associations

    private AssociationType type;

    private AssociationListener associationListener = null;

    private NettySctpManagementImpl management;

    // Is the Association been started by management?
    private volatile boolean started = false;
    // Is the Association up (connection is established)
    protected volatile boolean up = false;

    private NettySctpChannelInboundHandlerAdapter channelHandler;
    protected int congLevel;

    public NettyAssociationImpl() {
        super();
    }

    /**
     * Creating a CLIENT Association
     * 
     * @param hostAddress
     * @param hostPort
     * @param peerAddress
     * @param peerPort
     * @param assocName
     * @param ipChannelType
     * @param extraHostAddresses
     * @throws IOException
     */
    public NettyAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort,
            String assocName, IpChannelType ipChannelType, String[] extraHostAddresses) throws IOException {
        this();
        this.hostAddress = hostAddress;
        this.hostPort = hostPort;
        this.peerAddress = peerAddress;
        this.peerPort = peerPort;
        this.name = assocName;
        this.ipChannelType = ipChannelType;
        this.extraHostAddresses = extraHostAddresses;

        this.type = AssociationType.CLIENT;
    }

    /**
     * Creating a SERVER Association
     * 
     * @param peerAddress
     * @param peerPort
     * @param serverName
     * @param assocName
     * @param ipChannelType
     */
    public NettyAssociationImpl(String peerAddress, int peerPort, String serverName, String assocName,
            IpChannelType ipChannelType) {
        this();
        this.peerAddress = peerAddress;
        this.peerPort = peerPort;
        this.serverName = serverName;
        this.name = assocName;
        this.ipChannelType = ipChannelType;

        this.type = AssociationType.SERVER;

    }

    /**
     * Creating an ANONYMOUS_SERVER Association
     * 
     * @param hostAddress
     * @param hostPort
     * @param peerAddress
     * @param peerPort
     * @param serverName
     * @param assocName
     * @param ipChannelType
     */
    protected NettyAssociationImpl(String peerAddress, int peerPort, String serverName, IpChannelType ipChannelType,
            NettyServerImpl server) {
        this();
        this.peerAddress = peerAddress;
        this.peerPort = peerPort;
        this.serverName = serverName;
        this.ipChannelType = ipChannelType;
        this.server = server;

        this.type = AssociationType.ANONYMOUS_SERVER;

    }

    public NettySctpManagementImpl getManagement() {
        return management;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getIpChannelType()
     */
    @Override
    public IpChannelType getIpChannelType() {
        return this.ipChannelType;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getAssociationType()
     */
    @Override
    public AssociationType getAssociationType() {
        return this.type;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getName()
     */
    @Override
    public String getName() {
        return this.name;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#isStarted()
     */
    @Override
    public boolean isStarted() {
        return this.started;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#isConnected()
     */
    @Override
    public boolean isConnected() {
        return started && up;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#isUp()
     */
    @Override
    public boolean isUp() {
        return up;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getAssociationListener()
     */
    @Override
    public AssociationListener getAssociationListener() {
        return this.associationListener;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#setAssociationListener(org.mobicents.protocols.api.AssociationListener)
     */
    @Override
    public void setAssociationListener(AssociationListener associationListener) {
        this.associationListener = associationListener;

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getHostAddress()
     */
    @Override
    public String getHostAddress() {
        return hostAddress;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getHostPort()
     */
    @Override
    public int getHostPort() {
        return hostPort;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getPeerAddress()
     */
    @Override
    public String getPeerAddress() {
        return peerAddress;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getPeerPort()
     */
    @Override
    public int getPeerPort() {
        return peerPort;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getServerName()
     */
    @Override
    public String getServerName() {
        return serverName;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#getExtraHostAddresses()
     */
    @Override
    public String[] getExtraHostAddresses() {
        return extraHostAddresses;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#send(org.mobicents.protocols.api.PayloadData)
     */
    @Override
    public void send(PayloadData payloadData) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Tx : Ass=%s %s", this.getName(), payloadData));
        }

        NettySctpChannelInboundHandlerAdapter handler = checkSocketIsOpen();

        final ByteBuf byteBuf = payloadData.getByteBuf();
        if (this.ipChannelType == IpChannelType.SCTP) {
            SctpMessage sctpMessage = new SctpMessage(payloadData.getPayloadProtocolId(),
                    payloadData.getStreamNumber(), payloadData.isUnordered(), byteBuf);
            handler.writeAndFlush(sctpMessage);
        } else {
            handler.writeAndFlush(byteBuf);
        }
    }

    private NettySctpChannelInboundHandlerAdapter checkSocketIsOpen() throws Exception {
        NettySctpChannelInboundHandlerAdapter handler = this.channelHandler;
        if (!this.started || handler == null)
            throw new Exception(String.format(
                    "Association is not started or underlying sctp/tcp channel is down for Association=%s",
                    this.name));
        return handler;
    }

    @Override
    public ByteBufAllocator getByteBufAllocator() {
        if (this.channelHandler != null)
            return this.channelHandler.channel.alloc();
        else
            return null;
    }

    @Override
    public int getCongestionLevel() {
        return this.congLevel;
    }

    protected void setCongestionLevel(int val) {
        if (this.congLevel != val) {
            logger.warn("Outgoing congestion control: SCTP: Changing of congestion level for Association="
                    + this.name + " " + this.congLevel + "->" + val);
        }

        this.congLevel = val;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#acceptAnonymousAssociation(org.mobicents.protocols.api.AssociationListener)
     */
    @Override
    public void acceptAnonymousAssociation(AssociationListener associationListener) throws Exception {
        this.associationListener = associationListener;

        if (this.getAssociationType() != AssociationType.ANONYMOUS_SERVER) {
            throw new UnsupportedOperationException(
                    "Association.acceptAnonymousAssociation() can be applied only for anonymous associations");
        }

        this.start();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#rejectAnonymousAssociation()
     */
    @Override
    public void rejectAnonymousAssociation() {
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Association#stopAnonymousAssociation()
     */
    @Override
    public void stopAnonymousAssociation() throws Exception {
        if (this.getAssociationType() != AssociationType.ANONYMOUS_SERVER) {
            throw new UnsupportedOperationException(
                    "Association.stopAnonymousAssociation() can be applied only for anonymous associations");
        }

        this.stop();
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {

        StringBuilder sb = new StringBuilder();
        sb.append("Association [name=").append(this.name).append(", associationType=").append(this.type)
                .append(", ipChannelType=").append(this.ipChannelType).append(", hostAddress=")
                .append(this.hostAddress).append(", hostPort=").append(this.hostPort).append(", peerAddress=")
                .append(this.peerAddress).append(", peerPort=").append(this.peerPort).append(", serverName=")
                .append(this.serverName);

        sb.append(", extraHostAddress=[");

        if (this.extraHostAddresses != null) {
            for (int i = 0; i < this.extraHostAddresses.length; i++) {
                String extraHostAddress = this.extraHostAddresses[i];
                sb.append(extraHostAddress);
                sb.append(", ");
            }
        }

        sb.append("]]");

        return sb.toString();
    }

    /**
     * @param management the management to set
     */
    protected void setManagement(NettySctpManagementImpl management) {
        this.management = management;
    }

    protected void start() throws Exception {
        if (this.associationListener == null) {
            throw new NullPointerException(
                    String.format("AssociationListener is null for Associatoion=%s", this.name));
        }

        if (this.type == AssociationType.CLIENT) {
            this.scheduleConnect();
        }

        this.started = true;

        if (logger.isInfoEnabled()) {
            if (this.type != AssociationType.ANONYMOUS_SERVER) {
                logger.info(String.format("Started Association=%s", this));
            }
        }

        for (ManagementEventListener lstr : this.management.getManagementEventListeners()) {
            try {
                lstr.onAssociationStarted(this);
            } catch (Throwable ee) {
                logger.error("Exception while invoking onAssociationStarted", ee);
            }
        }
    }

    protected void stop() throws Exception {
        if (logger.isInfoEnabled()) {
            logger.info(String.format("Management requested to stop %s", this.toString()));
        }
        this.started = false;
        for (ManagementEventListener lstr : this.management.getManagementEventListeners()) {
            try {
                lstr.onAssociationStopped(this);
            } catch (Throwable ee) {
                logger.error("Exception while invoking onAssociationStopped", ee);
            }
        }

        NettySctpChannelInboundHandlerAdapter handler = this.channelHandler;
        if (handler != null) {
            handler.closeChannel();
        }
    }

    protected void read(PayloadData payload) {
        try {
            this.associationListener.onPayload(this, payload);
        } catch (Exception e) {
            logger.error(
                    String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, payload),
                    e);
        }
    }

    protected void markAssociationUp(int maxInboundStreams, int maxOutboundStreams) {
        if (this.server != null) {
            synchronized (this.server.anonymAssociations) {
                this.server.anonymAssociations.add(this);
            }
        }

        this.up = true;
        this.getAssociationListener().onCommunicationUp(this, maxInboundStreams, maxOutboundStreams);

        for (ManagementEventListener lstr : this.management.getManagementEventListeners()) {
            try {
                lstr.onAssociationUp(this);
            } catch (Throwable ee) {
                logger.error("Exception while invoking onAssociationUp", ee);
            }
        }
    }

    protected void markAssociationDown() {
        if (this.up) {
            // To avoid calling Listener again and again
            this.up = false;

            for (ManagementEventListener lstr : this.management.getManagementEventListeners()) {
                try {
                    lstr.onAssociationDown(this);
                } catch (Throwable ee) {
                    logger.error("Exception while invoking onAssociationDown", ee);
                }
            }

            this.getAssociationListener().onCommunicationShutdown(this);

            if (this.server != null) {
                synchronized (this.server.anonymAssociations) {
                    this.server.anonymAssociations.remove(this);
                }
            }
        }
    }

    protected void scheduleConnect() {
        int connectDelay = this.management.getConnectDelay();
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Scheduling of a channel connection: Association=%s, connectDelay=%d", this,
                    connectDelay));
        }

        //        final ScheduledExecutorService loop = this.management.getBossGroup().next();
        final ScheduledExecutorService loop = this.management.getClientExecutor();
        loop.schedule(new Runnable() {
            @Override
            public void run() {
                connect();
            }
        }, connectDelay, TimeUnit.MILLISECONDS);
    }

    protected void setChannelHandler(NettySctpChannelInboundHandlerAdapter channelHandler) {
        this.channelHandler = channelHandler;
    }

    protected void connect() {
        if (!this.started || this.up) {
            // return if not started or already up
            return;
        }

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Initiating connection started: Association=%s", this));
        }

        Bootstrap b;
        InetSocketAddress localAddress;
        try {
            EventLoopGroup group = this.management.getBossGroup();
            b = new Bootstrap();

            b.group(group);
            if (this.ipChannelType == IpChannelType.SCTP) {
                b.channel(NioSctpChannel.class);

                // applying of stack level SCTP options
                this.applySctpOptions(b);

                b.handler(new NettySctpClientChannelInitializer(this));
            } else {
                b.channel(NioSocketChannel.class);
                b.option(ChannelOption.TCP_NODELAY, true);
                b.handler(new NettyTcpClientChannelInitializer(this));
            }

            localAddress = new InetSocketAddress(this.hostAddress, this.hostPort);
        } catch (Exception e) {
            logger.error(String.format("Exception while creating connection for Association=%s", this.getName()),
                    e);
            this.scheduleConnect();
            return;
        }

        // Bind the client channel.
        try {
            ChannelFuture bindFuture = b.bind(localAddress).sync();
            Channel channel = bindFuture.channel();

            if (this.ipChannelType == IpChannelType.SCTP) {
                // Get the underlying sctp channel
                SctpChannel sctpChannel = (SctpChannel) channel;

                // Bind the secondary address.
                // Please note that, bindAddress in the client channel should be done before connecting if you have not
                // enable Dynamic Address Configuration. See net.sctp.addip_enable kernel param
                if (this.extraHostAddresses != null) {
                    for (int count = 0; count < this.extraHostAddresses.length; count++) {
                        String localSecondaryAddress = this.extraHostAddresses[count];
                        InetAddress localSecondaryInetAddress = InetAddress.getByName(localSecondaryAddress);

                        sctpChannel.bindAddress(localSecondaryInetAddress).sync();
                    }
                }
            }

            InetSocketAddress remoteAddress = new InetSocketAddress(this.peerAddress, this.peerPort);

            // Finish connect
            bindFuture.channel().connect(remoteAddress);
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Initiating connection scheduled: Association=%s remoteAddress=%s", this,
                        remoteAddress));
            }
        } catch (Exception e) {
            logger.error(String.format("Exception while finishing connection for Association=%s", this.getName()),
                    e);
        }
    }

    private void applySctpOptions(Bootstrap b) {
        b.option(SctpChannelOption.SCTP_NODELAY, this.management.getOptionSctpNodelay());
        b.option(SctpChannelOption.SCTP_DISABLE_FRAGMENTS, this.management.getOptionSctpDisableFragments());
        b.option(SctpChannelOption.SCTP_FRAGMENT_INTERLEAVE, this.management.getOptionSctpFragmentInterleave());
        b.option(SctpChannelOption.SCTP_INIT_MAXSTREAMS, this.management.getOptionSctpInitMaxstreams());
        b.option(SctpChannelOption.SO_SNDBUF, this.management.getOptionSoSndbuf());
        b.option(SctpChannelOption.SO_RCVBUF, this.management.getOptionSoRcvbuf());
        b.option(SctpChannelOption.SO_LINGER, this.management.getOptionSoLinger());
    }

    /**
     * XML Serialization/Deserialization
     */
    protected static final XMLFormat<NettyAssociationImpl> ASSOCIATION_XML = new XMLFormat<NettyAssociationImpl>(
            NettyAssociationImpl.class) {

        @SuppressWarnings("unchecked")
        @Override
        public void read(javolution.xml.XMLFormat.InputElement xml, NettyAssociationImpl association)
                throws XMLStreamException {
            association.name = xml.getAttribute(NAME, "");
            association.type = AssociationType.getAssociationType(xml.getAttribute(ASSOCIATION_TYPE, ""));
            association.hostAddress = xml.getAttribute(HOST_ADDRESS, "");
            association.hostPort = xml.getAttribute(HOST_PORT, 0);

            association.peerAddress = xml.getAttribute(PEER_ADDRESS, "");
            association.peerPort = xml.getAttribute(PEER_PORT, 0);

            association.serverName = xml.getAttribute(SERVER_NAME, "");
            association.ipChannelType = IpChannelType
                    .getInstance(xml.getAttribute(IPCHANNEL_TYPE, IpChannelType.SCTP.getCode()));
            if (association.ipChannelType == null)
                association.ipChannelType = IpChannelType.SCTP;

            int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0);
            association.extraHostAddresses = new String[extraHostAddressesSize];

            for (int i = 0; i < extraHostAddressesSize; i++) {
                association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class);
            }

        }

        @Override
        public void write(NettyAssociationImpl association, javolution.xml.XMLFormat.OutputElement xml)
                throws XMLStreamException {
            xml.setAttribute(NAME, association.name);
            xml.setAttribute(ASSOCIATION_TYPE, association.type.getType());
            xml.setAttribute(HOST_ADDRESS, association.hostAddress);
            xml.setAttribute(HOST_PORT, association.hostPort);

            xml.setAttribute(PEER_ADDRESS, association.peerAddress);
            xml.setAttribute(PEER_PORT, association.peerPort);

            xml.setAttribute(SERVER_NAME, association.serverName);
            xml.setAttribute(IPCHANNEL_TYPE, association.ipChannelType.getCode());

            xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE,
                    association.extraHostAddresses != null ? association.extraHostAddresses.length : 0);
            if (association.extraHostAddresses != null) {
                for (String s : association.extraHostAddresses) {
                    xml.add(s, EXTRA_HOST_ADDRESS, String.class);
                }
            }
        }
    };
}