android.gov.nist.javax.sip.stack.NIOHandler Maven / Gradle / Ivy
/*
* Conditions Of Use
*
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), an agency of the Federal Government.
* Pursuant to title 15 United States Code Section 105, works of NIST
* employees are not subject to copyright protection in the United States
* and are considered to be in the public domain. As a result, a formal
* license is not needed to use the software.
*
* This software is provided by NIST as a service and is expressly
* provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY. NIST does 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.
*
* Permission to use this software is contingent upon your acceptance
* of the terms of this agreement
*
* .
*
*/
/*******************************************************************************
* Product of NIST/ITL Advanced Networking Technologies Division (ANTD). *
*******************************************************************************/
package android.gov.nist.javax.sip.stack;
import android.gov.nist.core.CommonLogger;
import android.gov.nist.core.LogLevels;
import android.gov.nist.core.LogWriter;
import android.gov.nist.core.StackLogger;
import android.gov.nist.javax.sip.SipStackImpl;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Low level Input output to a socket. Caches TCP connections and takes care of
* re-connecting to the remote party if the other end drops the connection
*
* @version 1.2
*
* @author Vladimir Ralev
*
*
*/
public class NIOHandler {
private static StackLogger logger = CommonLogger.getLogger(NIOHandler.class);
private static final int BLOCKING_CONNECT_TIMEOUT = 10000;
private SipStackImpl sipStack;
private NioTcpMessageProcessor messageProcessor;
private AtomicBoolean stopped=new AtomicBoolean(false);
// A cache of client sockets that can be re-used for
// sending tcp messages.
private final ConcurrentHashMap socketTable = new ConcurrentHashMap();
KeyedSemaphore keyedSemaphore = new KeyedSemaphore();
protected static String makeKey(InetAddress addr, int port) {
return addr.getHostAddress() + ":" + port;
}
protected static String makeKey(String addr, int port) {
return addr + ":" + port;
}
protected NIOHandler(SIPTransactionStack sipStack, NioTcpMessageProcessor messageProcessor) {
this.sipStack = (SipStackImpl) sipStack;
this.messageProcessor = messageProcessor;
}
/**
* Thread safety delivered through keyedSemaphore.
* @param key
* @param sock
*/
protected void putSocket(String key, SocketChannel sock) {
if(stopped.get())
return;
if (logger.isLoggingEnabled(StackLogger.TRACE_DEBUG)) {
logger.logDebug("adding socket for key " + key);
}
boolean entered = false;
try {
keyedSemaphore.enterIOCriticalSection(key);
entered = true;
socketTable.put(key, sock);
} catch (IOException ioExp) {
//this maybe a fair situation, so log under debug
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logError("Failed on putting socket", ioExp);
}
} finally {
if (entered) {
keyedSemaphore.leaveIOCriticalSection(key);
}
}
}
protected SocketChannel getSocket(String key) {
// no need to synchrnize here
return (SocketChannel) socketTable.get(key);
}
/**
* This is where actual removal is done.
*
* The semaphore is aquired to coordinate with openOutgoingConnection threads.
*
*
* @param key
*/
private void removeSocket(String key) {
boolean entered = false;
try {
keyedSemaphore.enterIOCriticalSection(key);
entered = true;
SocketChannel removed = socketTable.remove(key);
keyedSemaphore.remove(key);
if (logger.isLoggingEnabled(StackLogger.TRACE_DEBUG)) {
boolean wasRemoved = removed != null;
logger.logDebug("removed Socket and Semaphore for key " + key + ", removed:" + wasRemoved);
}
} catch (IOException ioExp) {
//this maybe a fair situation, so log under debug
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logError("Failed on putting socket", ioExp);
}
} finally {
if (entered) {
keyedSemaphore.leaveIOCriticalSection(key);
}
}
}
/**
* Searches matching socket in internal map, and then removes from internal
* structures.
*
* This method is thread-safe by underlying concurrent Map impl.
* Even if more than one thread try to removeSocket concurrently, the
* code will be run safely with idempotence property. If last thread manages
* to locate the same map entries to remove than first thread, the map.remove
* will simply be ignored...
*
* @param channel
*/
protected void removeSocket(SocketChannel channel) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("Trying to remove cached socketChannel without key"
+ this + " socketChannel = " + channel);
}
LinkedList keys = new LinkedList();
//this iteration is thread safe since we are using concurrent map
Set> e = socketTable.entrySet();
for(Entry entry : e ) {
SocketChannel sc = entry.getValue();
if(sc.equals(channel)) {
keys.add(entry.getKey());
}
}
for(String key : keys) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("Removing cached socketChannel without key"
+ this + " socketChannel = " + channel + " key = " + key);
}
removeSocket(key);
}
}
/**
* A private function to write things out. This needs to be synchronized as
* writes can occur from multiple threads. We write in chunks to allow the
* other side to synchronize for large sized writes.
*/
private void writeChunks(SocketChannel channel, byte[] bytes, int length) {
//Code simplified and method kept for historical reason.
//The chunking was not taking place anyway.
messageProcessor.send(channel, bytes);
}
/**
*
* @param senderAddress
* @param receiverAddress
* @param contactPort
* @param retry
* @param key
* @return
* @throws IOException
*/
private SocketChannel openOutgoingConnection(InetAddress senderAddress,
InetAddress receiverAddress, int contactPort, boolean retry, String key) throws IOException {
int retry_count = 0;
int max_retry = retry ? 2 : 1;
SocketChannel clientSock = null;
boolean entered = false;
try {
keyedSemaphore.enterIOCriticalSection(key);
entered = true;
//we need to check again, now we are in proper critical sec
clientSock = getSocket(key);
if(clientSock != null && (!clientSock.isConnected() || !clientSock.isOpen())) {
removeSocket(key);
clientSock = null;
}
if(clientSock == null) {
//ok, this thread won, let's try to recover conn
while (retry_count < max_retry) {
if (clientSock == null) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug(
"inaddr = " + receiverAddress);
logger.logDebug(
"port = " + contactPort);
}
// note that the IP Address for stack may not be
// assigned.
// sender address is the address of the listening point.
// in version 1.1 all listening points have the same IP
// address (i.e. that of the stack). In version 1.2
// the IP address is on a per listening point basis.
try {
clientSock = messageProcessor.blockingConnect(new InetSocketAddress(receiverAddress, contactPort), senderAddress, BLOCKING_CONNECT_TIMEOUT);
//sipStack.getNetworkLayer().createSocket(
// receiverAddress, contactPort, senderAddress); TODO: sender address needed
} catch (SocketException e) { // We must catch the socket timeout exceptions here, any SocketException not just ConnectException
logger.logError("Problem connecting " +
receiverAddress + " " + contactPort + " " + senderAddress );
// new connection is bad.
// remove from our table the socket and its semaphore
removeSocket(key);
throw new SocketException(e.getClass() + " " + e.getMessage() + " " + e.getCause() + " Problem connecting " +
receiverAddress + " " + contactPort + " " + senderAddress);
}
putSocket(key, clientSock);
break;
} else {
break;
}
}
}
} catch (IOException ex) {
if (logger.isLoggingEnabled(LogWriter.TRACE_ERROR)) {
logger.logError(
"Problem OpeningConn: "
+ " inAddr "
+ receiverAddress.getHostAddress()
+ " port = " + contactPort + " retry " + retry);
}
removeSocket(key);
/*
* For TCP responses, the transmission of responses is
* controlled by RFC 3261, section 18.2.2 :
*
* o If the "sent-protocol" is a reliable transport protocol
* such as TCP or SCTP, or TLS over those, the response MUST be
* sent using the existing connection to the source of the
* original request that created the transaction, if that
* connection is still open. This requires the server transport
* to maintain an association between server transactions and
* transport connections. If that connection is no longer open,
* the server SHOULD open a connection to the IP address in the
* "received" parameter, if present, using the port in the
* "sent-by" value, or the default port for that transport, if
* no port is specified. If that connection attempt fails, the
* server SHOULD use the procedures in [4] for servers in order
* to determine the IP address and port to open the connection
* and send the response to.
*/
if (!retry) {
if (contactPort <= 0)
contactPort = 5060;
key = makeKey(receiverAddress, contactPort);
clientSock = this.getSocket(key);
if (clientSock == null || !clientSock.isConnected() || !clientSock.isOpen()) {
removeSocket(key);
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug(
"inaddr = " + receiverAddress +
" port = " + contactPort);
}
clientSock = messageProcessor.blockingConnect(new InetSocketAddress(receiverAddress, contactPort), senderAddress, BLOCKING_CONNECT_TIMEOUT);
putSocket(key, clientSock);
}
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug(
"sending to " + key );
}
} else {
logger.logError("IOException occured at " , ex);
throw ex;
}
} finally {
if (entered) {
keyedSemaphore.leaveIOCriticalSection(key);
}
}
return clientSock;
}
/**
* Send an array of bytes.
*
* @param receiverAddress
* -- inet address
* @param contactPort
* -- port to connect to.
* @param transport
* -- tcp or udp.
* @param retry
* -- retry to connect if the other end closed connection
* @throws IOException
* -- if there is an IO exception sending message.
*/
public SocketChannel sendBytes(InetAddress senderAddress,
InetAddress receiverAddress, int contactPort, String transport,
byte[] bytes, boolean retry, NioTcpMessageChannel messageChannel)
throws IOException {
if(stopped.get())
return null;
// Server uses TCP transport. TCP client sockets are cached
int length = bytes.length;
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug(
"sendBytes " + transport + " inAddr "
+ receiverAddress.getHostAddress() + " port = "
+ contactPort + " length = " + length + " retry " + retry );
}
if (logger.isLoggingEnabled(LogLevels.TRACE_INFO)
&& sipStack.isLogStackTraceOnMessageSend()) {
logger.logStackTrace(StackLogger.TRACE_INFO);
}
String key = makeKey(receiverAddress, contactPort);
boolean newSocket = false;
SocketChannel clientSock = getSocket(key);
if(clientSock != null && (!clientSock.isConnected() || !clientSock.isOpen())) {
clientSock = null;
}
if(clientSock == null) {
newSocket = true;
clientSock = openOutgoingConnection(senderAddress, receiverAddress, contactPort, retry, key);
messageChannel.peerPort = contactPort;
}
if(clientSock != null) {
if(newSocket && messageChannel instanceof NioTlsMessageChannel) {
//We dont write data when using TLS, the new socket needs to handshake first
//Added for https://java.net/jira/browse/JSIP-483
HandshakeCompletedListenerImpl listner = new HandshakeCompletedListenerImpl((NioTlsMessageChannel)messageChannel, clientSock);
((NioTlsMessageChannel) messageChannel)
.setHandshakeCompletedListener(listner);
} else {
writeChunks(clientSock, bytes, length);
}
}
if (clientSock == null) {
if (logger.isLoggingEnabled(LogWriter.TRACE_ERROR)) {
logger.logError(
this.socketTable.toString());
logger.logError(
"Could not connect to " + receiverAddress + ":"
+ contactPort);
}
throw new IOException("Could not connect to " + receiverAddress
+ ":" + contactPort);
} else {
return clientSock;
}
}
/**
* Close all the cached connections.
*/
public void closeAll() {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger
.logDebug(
"Closing " + socketTable.size()
+ " sockets from IOHandler");
for (Enumeration values = socketTable.elements(); values
.hasMoreElements();) {
SocketChannel s = (SocketChannel) values.nextElement();
try {
s.close();
} catch (IOException ex) {
}
}
}
public void stop() {
stopped.set(true);
try {
// Reworked the method for https://java.net/jira/browse/JSIP-471
if(logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("keys to check for inactivity removal " + NioTcpMessageChannel.channelMap.keySet());
logger.logDebug("existing socket in NIOHandler " + socketTable.keySet());
}
Iterator> entriesIterator = NioTcpMessageChannel.channelMap.entrySet().iterator();
while(entriesIterator.hasNext()) {
Entry entry = entriesIterator.next();
SocketChannel socketChannel = entry.getKey();
NioTcpMessageChannel messageChannel = entry.getValue();
if(logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("stop() : Removing socket " + messageChannel.key
+ " socketChannel = " + socketChannel);
}
messageChannel.close();
NioTcpMessageChannel.channelMap.remove(socketChannel);
entriesIterator = NioTcpMessageChannel.channelMap.entrySet().iterator();
}
} catch (Exception e) {
}
}
public SocketChannel createOrReuseSocket(InetAddress inetAddress, int port) throws IOException {
if(stopped.get())
return null;
String key = NIOHandler.makeKey(inetAddress, port);
SocketChannel channel = null;
channel = getSocket(key);
if(channel != null && (!channel.isConnected() || !channel.isOpen())) {
if(logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("Channel disconnected " + channel);
channel = null;
}
if(channel == null) { // this is where the threads will race
channel = openOutgoingConnection(this.messageProcessor
.getIpAddress(), inetAddress, port, false, key);
}
if(logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("Returning socket " + key + " channel = " + channel);
return channel;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy