/* * 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. */ /* * File Name : JainMgcpStackImpl.java * * The JAIN MGCP API implementaion. * * The source code contained in this file is in in the public domain. * It can be used in any project or product without prior permission, * license or royalty payments. There is NO WARRANTY OF ANY KIND, * EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, * THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, * AND DATA ACCURACY. We do not warrant or make any representations * regarding the use of the software or the results thereof, including * but not limited to the correctness, accuracy, reliability or * usefulness of the software. */ package org.restcomm.media.client.mgcp.stack; import java.io.IOException; import java.io.InputStream; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.util.Properties; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.restcomm.media.client.mgcp.handlers.MessageHandler; import org.restcomm.media.client.mgcp.handlers.TransactionHandler; import org.restcomm.media.client.mgcp.utils.PacketRepresentation; import org.restcomm.media.client.mgcp.utils.PacketRepresentationFactory; import org.restcomm.media.concurrent.ConcurrentCyclicFIFO; import org.restcomm.media.concurrent.ConcurrentMap; import jain.protocol.ip.mgcp.CreateProviderException; import jain.protocol.ip.mgcp.DeleteProviderException; import jain.protocol.ip.mgcp.JainMgcpProvider; import jain.protocol.ip.mgcp.JainMgcpStack; import jain.protocol.ip.mgcp.OAM_IF; /** * * @author Oleg Kulikov * @author Pavel Mitrenko * @author Yulian Oifa */ public class JainMgcpStackImpl extends Thread implements JainMgcpStack, OAM_IF { // Static variables from properties files /** * Defines how many executors will work on event delivery */ public static final String _EXECUTOR_TABLE_SIZE = "executorTableSize"; /** * Defines how many message can be stored in queue before new ones are discarded. */ public static final String _EXECUTOR_QUEUE_SIZE = "executorQueueSize"; private static final Logger logger = Logger.getLogger(JainMgcpStackImpl.class); private static final String propertiesFileName = "mgcp-stack.properties"; private String protocolVersion = "1.0"; protected int port = 2727; private InetAddress localAddress = null; private boolean stopped = true; private PacketRepresentationFactory prFactory = null; // issue #7: https://bitbucket.org/telestax/telscale-media-server/issue/7/buffer-size-for-webrtc-calls-is-too-small // hrosa - Increased the buffer size from 5000 to 11000 to accommodate SDP in WebRTC calls private static final int BUFFER_SIZE = 11000; private DatagramChannel channel; // private Selector selector; ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE); ByteBuffer sendBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE); byte[] b = new byte[BUFFER_SIZE]; // For now we have only one provider/delete prvider method wont work. public JainMgcpStackProviderImpl provider = null; private InetSocketAddress address = null; /** * holds current active transactions (RFC 3435 [$3.2.1.2]: for tx sent & received). * */ private ConcurrentMap<TransactionHandler> localTransactions = new ConcurrentMap<TransactionHandler>(); private ConcurrentMap<Integer> remoteTxToLocalTxMap = new ConcurrentMap<Integer>(); private ConcurrentMap<TransactionHandler> completedTransactions = new ConcurrentMap<TransactionHandler>(); private ConcurrentCyclicFIFO<PacketRepresentation> inputQueue=new ConcurrentCyclicFIFO<PacketRepresentation>(); private DatagramSocket socket; private long delay = 4; private long threshold = 2; protected int parserThreadPoolSize = 2; private DecodingThread[] decodingThreads; public void printStats() { //System.out.println("localTransactions size = " + localTransactions.size()); //System.out.println("remoteTxToLocalTxMap size = " + remoteTxToLocalTxMap.size()); //System.out.println("completedTransactions size = " + completedTransactions.size()); } // Defualt constructor for TCK public JainMgcpStackImpl() { } /** Creates a new instance of JainMgcpStackImpl */ public JainMgcpStackImpl(InetAddress localAddress, int port) { this.localAddress = localAddress; this.port = port; } private void init() throws IOException { readProperties(); // initExecutors(); if (channel == null) { try { InetSocketAddress bindAddress = new InetSocketAddress(this.localAddress, this.port); this.channel = DatagramChannel.open(); socket = this.channel.socket(); socket.bind(bindAddress); this.channel.configureBlocking(false); this.localAddress = socket.getLocalAddress(); logger.info("Jain Mgcp stack bound to IP " + this.localAddress + " and UDP port " + this.port); // This is for TCK don't remove System.out.println("Jain Mgcp stack bound to IP " + this.localAddress + " and UDP port " + this.port); } catch (SocketException e) { logger.error(e); throw new RuntimeException("Failed to find a local port " + this.port + " to bound stack"); } } stopped = false; if (logger.isDebugEnabled()) { logger.debug("Starting main thread " + this); } this.provider = new JainMgcpStackProviderImpl(this); this.prFactory = new PacketRepresentationFactory(50, BUFFER_SIZE); decodingThreads=new DecodingThread[this.parserThreadPoolSize]; for(int i=0;i<decodingThreads.length;i++) decodingThreads[i]=new DecodingThread(this); // So stack does not die this.setDaemon(false); start(); } private void readProperties() { try { Properties props = new Properties(); InputStream is = this.getClass().getClassLoader().getResourceAsStream(this.propertiesFileName); if (is == null) { logger.warn("Failed to locate properties file, using default values"); return; } props.load(is); String val = null; val = props.getProperty(_EXECUTOR_QUEUE_SIZE, "" + this.parserThreadPoolSize); this.parserThreadPoolSize = Integer.parseInt(val); val = null; logger.info(this.propertiesFileName + " read successfully! \nexecutorQueueSize = " + this.parserThreadPoolSize); } catch (Exception e) { logger.warn("Failed to read properties file due to some error \"" + e.getMessage() + "\", using defualt values!!!!"); } } /** * Closes the stack and it's underlying resources. */ public void close() { stopped = true; try { if (logger.isDebugEnabled()) { logger.debug("Closing socket"); } // selector.close(); socket.close(); if (this.channel != null) { this.channel.close(); } } catch (Exception e) { if(logger.isEnabledFor(Level.ERROR)) { logger.error("Could not gracefully close socket", e); } } } public JainMgcpProvider createProvider() throws CreateProviderException { if (this.provider != null) { throw new CreateProviderException( "Provider already created. Only 1 provider can be created. Delete the first and then re-create"); } try { init(); } catch (IOException e) { if(logger.isEnabledFor(Level.ERROR)) { logger.error("Failed to open Socket ", e); } throw new CreateProviderException(e.getMessage()); } return this.provider; } public void deleteProvider(JainMgcpProvider provider) throws DeleteProviderException { if (this.provider == null) { throw new DeleteProviderException("No Provider exist."); } if (this.provider != provider) { throw new DeleteProviderException("Passed provider is not current one."); } this.close(); this.provider = null; } public void setPort(int port) { this.port = port; } public int getPort() { return port; } public InetAddress getAddress() { if (this.localAddress != null) { return this.localAddress; } else { return null; } } public String getProtocolVersion() { return protocolVersion; } public void setProtocolVersion(String protocolVersion) { this.protocolVersion = protocolVersion; } public PacketRepresentation allocatePacket() { return this.prFactory.allocate(); } public void releasePacket(PacketRepresentation pr) { this.prFactory.deallocate(pr); } public void send(PacketRepresentation pr) { try { this.channel.send(pr.getBuffer(), pr.getInetAddress()); } catch (Exception e) { logger.error("Could not send data " + pr.getBuffer().toString() + " to " + pr.getInetAddress().toString(), e); } } public boolean isRequest(String header) { return header.matches("[\\w]{4}(\\s|\\S)*"); } @Override public void run() { if (logger.isDebugEnabled()) { logger.debug("MGCP stack started successfully on " + this.localAddress + ":" + this.port); } int length = 0; long start = 0; long finish = 0; long drift = 0; long latency = 0; for(int i=0;i<this.decodingThreads.length;i++) this.decodingThreads[i].activate(); if(this.provider!=null) this.provider.start(); start = System.currentTimeMillis(); while (!stopped) { try { PacketRepresentation pr; do { this.receiveBuffer.clear(); address = (InetSocketAddress) this.channel.receive(this.receiveBuffer); this.receiveBuffer.flip(); length = this.receiveBuffer.limit(); if (length != 0) { pr = this.prFactory.allocate(); receiveBuffer.get(pr.getRawData(), 0, length); pr.setLength(length); pr.setRemoteAddress(address); inputQueue.offer(pr); } } while (this.address != null); //this is for async send this.provider.flush(); finish = System.currentTimeMillis(); drift = finish - start; latency = delay - drift; start = finish; if (drift <= threshold) { try { Thread.currentThread().sleep(latency); } catch (InterruptedException e) { return; } } } catch (IOException e) { if (stopped) { break; } if(logger.isEnabledFor(Level.ERROR)) { logger.error("I/O exception occured:", e); } continue; }catch(Exception e) { //catch everything, so worker wont die. if (stopped) { break; } if(logger.isEnabledFor(Level.ERROR)) { logger.error("Unexpected exception occured:", e); } continue; } } for(int i=0;i<this.decodingThreads.length;i++) this.decodingThreads[i].shutdown(); if(this.provider!=null) this.provider.stop(); if (logger.isDebugEnabled()) { logger.debug("MGCP stack stopped gracefully on" + this.localAddress + ":" + this.port); } } public ConcurrentMap<TransactionHandler> getLocalTransactions() { return localTransactions; } public ConcurrentMap<Integer> getRemoteTxToLocalTxMap() { return remoteTxToLocalTxMap; } public ConcurrentMap<TransactionHandler> getCompletedTransactions() { return completedTransactions; } private class DecodingThread extends Thread { private volatile boolean active; private JainMgcpStackImpl stack; protected MessageHandler messageHandler = null; public DecodingThread(JainMgcpStackImpl stack) { this.stack=stack; messageHandler=new MessageHandler(stack); } public void run() { PacketRepresentation current; while(active) { current=null; while(current==null) { try { current=inputQueue.take(); } catch(Exception e) { } } try { messageHandler.scheduleMessages(current); } catch(Exception e) { //catch everything, so worker wont die. if(logger.isEnabledFor(Level.ERROR)) logger.error("Unexpected exception occured:", e); } } } public void activate() { this.active = true; this.start(); } /** * Terminates thread. */ private void shutdown() { this.active = false; } } }