/* * 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.jdiameter.client.impl.transport.sctp; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import org.jdiameter.api.AvpDataException; import org.jdiameter.client.api.io.NotInitializedException; import org.mobicents.protocols.api.Association; import org.mobicents.protocols.api.AssociationListener; import org.mobicents.protocols.api.IpChannelType; import org.mobicents.protocols.api.PayloadData; import org.mobicents.protocols.sctp.AssociationImpl; import org.mobicents.protocols.sctp.ManagementImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a> * @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a> */ public class SCTPTransportClient { // How long to wait for the association to be connected private static final int CONNECT_TIMEOUT = 30000; // The waiting time between checks for association connection private static final int DELAY = 100; private ManagementImpl management = null; private AssociationImpl clientAssociation = null; private SCTPClientConnection parentConnection; private String clientAssociationName; protected InetSocketAddress destAddress; protected InetSocketAddress origAddress; private int payloadProtocolId = 0; private int streamNumber = 0; private static final Logger logger = LoggerFactory.getLogger(SCTPTransportClient.class); public SCTPTransportClient() { } /** * Default constructor * * @param concurrentFactory * factory for create threads * @param parenConnection * connection created this transport */ SCTPTransportClient(SCTPClientConnection parenConnection) { this.parentConnection = parenConnection; } public void initialize() throws IOException, NotInitializedException { logger.debug("Initializing SCTPTransportClient. Origin address is [{}] and destination address is [{}]", origAddress, destAddress); if (destAddress == null) { throw new NotInitializedException("Destination address is not set"); } logger.debug("Initializing SCTP client"); clientAssociationName = origAddress.getAddress().getHostAddress() + "." + origAddress.getPort() + "_" + destAddress.getAddress().getHostAddress() + "." + destAddress.getPort(); try { if (this.management == null) { this.management = new ManagementImpl(clientAssociationName); this.management.setSingleThread(true); this.management.start(); this.management.setConnectDelay(1000); // Try connecting every 1 secs -- Note: 1st attempt is also delayed! // Clear any saved connections, we will get them from jdiameter-config.xml this.management.removeAllResourses(); logger.debug("Management initialized."); } else { logger.debug("Management already initialized."); } if (this.clientAssociation == null) { logger.debug("Creating CLIENT ASSOCIATION '{}'. Origin Address [{}] <=> Dest Address [{}]", new Object[] { clientAssociationName, origAddress, destAddress }); this.clientAssociation = this.management.addAssociation(origAddress.getAddress().getHostAddress(), origAddress.getPort(), destAddress.getAddress().getHostAddress(), destAddress.getPort(), clientAssociationName, IpChannelType.SCTP, null); } else { logger.debug("CLIENT ASSOCIATION '{}'. Origin Address [{}:{}] <=> Dest Address [{}:{}] already present. Re-using it.", new Object[] { clientAssociation.getName(), clientAssociation.getHostAddress(), clientAssociation.getHostPort(), clientAssociation.getPeerAddress(), clientAssociation.getPeerPort() }); } } catch (Exception e) { logger.error("Failed to initialize client ", e); } } public SCTPClientConnection getParent() { return parentConnection; } public void start() throws NotInitializedException, IOException { // for client logger.debug("Starting SCTP client"); try { this.clientAssociation.setAssociationListener(new ClientAssociationListener()); this.management.startAssociation(clientAssociationName); } catch (Exception e) { logger.error("Failed to start client ", e); } if (getParent() == null) { throw new NotInitializedException("No parent connection is set"); } logger.debug("Successfuly initialized SCTP Client Host [{}:{}] Peer [{}:{}]", new Object[] { clientAssociation.getHostAddress(), clientAssociation.getHostPort(), clientAssociation.getPeerAddress(), clientAssociation.getPeerPort() }); logger.debug("Client Association Status: Started[{}] Connected[{}] Up[{}] ", new Object[]{clientAssociation.isStarted(), clientAssociation.isConnected(), clientAssociation.isUp()}); logger.trace("Client Association [{}]", clientAssociation); defer(); } private void defer() throws IOException { final long endTStamp = System.currentTimeMillis() + CONNECT_TIMEOUT; while (clientAssociation.isStarted() && !clientAssociation.isConnected() && !clientAssociation.isUp()) { try { Thread.sleep(DELAY); } catch (InterruptedException e) { // clear flag and proceed Thread.interrupted(); throw new IOException("Failed to establish SCTP connection, thread was interrupted waiting for connection."); } if (endTStamp < System.currentTimeMillis()) { throw new IOException("Failed to establish SCTP connection!"); } } logger.debug("Client Association Status: Started[{}] Connected[{}] Up[{}] ", new Object[]{clientAssociation.isStarted(), clientAssociation.isConnected(), clientAssociation.isUp()}); logger.trace("Client Association [{}]", clientAssociation); } private class ClientAssociationListener implements AssociationListener { private final Logger logger = LoggerFactory.getLogger(ClientAssociationListener.class); /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onCommunicationUp(org.mobicents.protocols.api.Association, int, int) */ @Override public void onCommunicationUp(Association association, int maxInboundStreams, int maxOutboundStreams) { logger.debug("onCommunicationUp called for [{}]", this); getParent().onConnected(); } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onCommunicationShutdown(org.mobicents.protocols.api.Association) */ @Override public void onCommunicationShutdown(Association association) { logger.debug("onCommunicationShutdown called for [{}]", this); try { getParent().onDisconnect(); } catch (Exception e) { logger.debug("Error", e); } } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onCommunicationLost(org.mobicents.protocols.api.Association) */ @Override public void onCommunicationLost(Association association) { logger.debug("onCommunicationLost called for [{}]", this); } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onCommunicationRestart(org.mobicents.protocols.api.Association) */ @Override public void onCommunicationRestart(Association association) { logger.debug("onCommunicationRestart called for [{}]", this); } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onPayload(org.mobicents.protocols.api.Association, * org.mobicents.protocols.api.PayloadData) */ @Override public void onPayload(Association association, PayloadData payloadData) { byte[] data = new byte[payloadData.getDataLength()]; System.arraycopy(payloadData.getData(), 0, data, 0, payloadData.getDataLength()); logger.debug("SCTP Client received data of length [{}]", data.length); try { // make a message out of data and process it getParent().onMessageReceived(ByteBuffer.wrap(data)); } catch (AvpDataException e) { logger.debug("Garbage was received. Discarding."); // storage.clear(); getParent().onAvpDataException(e); } } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#inValidStreamId(org.mobicents.protocols.api.PayloadData) */ @Override public void inValidStreamId(PayloadData payloadData) { // NOP ? } } public void stop() throws Exception { // Stop the SCTP this.management.stopAssociation(clientAssociationName); } public void release() throws Exception { this.stop(); this.management.removeAssociation(clientAssociationName); this.management.stop(); this.clientAssociation = null; } public InetSocketAddress getDestAddress() { return this.destAddress; } public void setDestAddress(InetSocketAddress address) { this.destAddress = address; if (logger.isDebugEnabled()) { logger.debug("Destination address is set to [{}:{}]", destAddress.getHostName(), destAddress.getPort()); } } public void setOrigAddress(InetSocketAddress address) { this.origAddress = address; if (logger.isDebugEnabled()) { logger.debug("Origin address is set to [{}:{}]", origAddress.getHostName(), origAddress.getPort()); } } public InetSocketAddress getOrigAddress() { return this.origAddress; } public void sendMessage(ByteBuffer bytes) throws IOException { if (logger.isDebugEnabled()) { logger.debug("About to send a byte buffer of size [{}] over the SCTP", bytes.array().length); } PayloadData payloadData = new PayloadData(bytes.array().length, bytes.array(), true, false, payloadProtocolId, streamNumber); try { this.clientAssociation.send(payloadData); } catch (Exception e) { logger.error("Failed sending byte buffer over SCTP", e); } if (logger.isDebugEnabled()) { logger.debug("Sent a byte buffer of size [{}] over SCTP", bytes.array().length); } } boolean isConnected() { return clientAssociation != null && this.clientAssociation.isConnected(); } }