* input - dealing with things like line continuation
*/
public void run() {
Pipeline inputStream = this.rawInputStream;
final StackLogger stackLogger = logger;
// inputStream = new MyFilterInputStream(this.rawInputStream);
// I cannot use buffered reader here because we may need to switch
// encodings to read the message body.
try {
isRunning = true;
while (isRunning) {
this.sizeCounter = this.maxMessageSize;
// this.messageSize = 0;
StringBuilder inputBuffer = new StringBuilder();
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug("Starting to parse.");
}
String line1;
String line2 = null;
boolean isPreviousLineCRLF = false;
while (true) {
try {
line1 = readLine(inputStream);
// ignore blank lines.
if (line1.equals("\n")) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug("Discarding blank line");
}
continue;
} else if(CRLF.equals(line1)) {
if(isPreviousLineCRLF) {
// Handling keepalive ping (double CRLF) as defined per RFC 5626 Section 4.4.1
// sending pong (single CRLF)
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("KeepAlive Double CRLF received, sending single CRLF as defined per RFC 5626 Section 4.4.1");
logger.logDebug("~~~ setting isPreviousLineCRLF=false");
}
isPreviousLineCRLF = false;
try {
sipMessageListener.sendSingleCLRF();
} catch (Exception e) {
logger.logError("A problem occured while trying to send a single CLRF in response to a double CLRF", e);
}
continue;
} else {
isPreviousLineCRLF = true;
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug("Received CRLF");
}
if(sipMessageListener != null &&
sipMessageListener instanceof ConnectionOrientedMessageChannel) {
((ConnectionOrientedMessageChannel)sipMessageListener).cancelPingKeepAliveTimeoutTaskIfStarted();
}
}
continue;
} else
break;
} catch (IOException ex) {
// we only wait if the thread is still in a running state and hasn't been close from somewhere else
// or we are leaking because the thread is waiting forever
if(postParseExecutor != null && isRunning){
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("waiting for messagesOrderingMap " + this + " threadname " + mythread.getName());
synchronized (messagesOrderingMap) {
try {
messagesOrderingMap.wait(64000);
} catch (InterruptedException e) {}
}
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("got notified for messagesOrderingMap " + this + " threadname " + mythread.getName());
}
this.rawInputStream.stopTimer();
if (logger.isLoggingEnabled(StackLogger.TRACE_DEBUG)) {
logger.logDebug("thread ending for threadname " + mythread.getName());
}
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logStackTrace(LogLevels.TRACE_DEBUG);
}
return;
}
}
inputBuffer.append(line1);
// Guard against bad guys.
this.rawInputStream.startTimer();
int bytesRead = 0;
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug("Reading Input stream.");
}
while (true) {
try {
line2 = readLine(inputStream);
bytesRead += line2.length();
if(maxMessageSize>0 && bytesRead> (maxMessageSize/2) ) throw new IOException("Pre-content-length headers size exceeded. The size of the message of the headers prior to Content-Length is too large. This must be an invalid message. Limit is MAX_MESSAGE_SIZE/2=" + maxMessageSize/2);
inputBuffer.append(line2);
if (line2.trim().equals(""))
break;
} catch (IOException ex) {
// we only wait if the thread is still in a running state and hasn't been close from somewhere else
// or we are leaking because the thread is waiting forever
if(postParseExecutor != null && isRunning){
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("waiting for messagesOrderingMap " + this + " threadname " + mythread.getName());
synchronized (messagesOrderingMap) {
try {
messagesOrderingMap.wait(64000);
} catch (InterruptedException e) {}
}
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("got notified for messagesOrderingMap " + this + " threadname " + mythread.getName());
}
this.rawInputStream.stopTimer();
if (logger.isLoggingEnabled(StackLogger.TRACE_DEBUG)) {
logger.logDebug("thread ending for threadname " + mythread.getName());
}
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logStackTrace(LogLevels.TRACE_DEBUG);
}
return;
}
}
// Stop the timer that will kill the read.
this.rawInputStream.stopTimer();
inputBuffer.append(line2);
// smp.setParseExceptionListener(sipMessageListener);
// smp.setReadBody(false);
SIPMessage sipMessage = null;
try {
if (stackLogger.isLoggingEnabled(StackLogger.TRACE_DEBUG)) {
stackLogger.logDebug("About to parse : " + inputBuffer.toString());
}
sipMessage = smp.parseSIPMessage(inputBuffer.toString().getBytes(), false, false, sipMessageListener);
if (sipMessage == null) {
this.rawInputStream.stopTimer();
continue;
}
} catch (ParseException ex) {
// Just ignore the parse exception.
stackLogger.logError("Detected a parse error", ex);
continue;
}
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug("Completed parsing message");
}
String clString = sipMessage.getHeaderAsFormattedString(ContentLength.NAME);
if(clString.length()>30) throw new RuntimeException("Bad content lenght header " + clString);
ContentLength cl = (ContentLength) sipMessage
.getContentLength();
int contentLength = 0;
if (cl != null) {
contentLength = cl.getContentLength();
} else {
contentLength = 0;
}
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug("Content length = " + contentLength);
}
if(maxMessageSize > 0 && contentLength > maxMessageSize) throw new RuntimeException("Max content size Exceeded! :" + contentLength + " allowed max size is " + maxMessageSize);
if (contentLength == 0) {
sipMessage.removeContent();
} else if (maxMessageSize == 0
|| contentLength < this.sizeCounter) {
byte[] message_body = new byte[contentLength];
int nread = 0;
while (nread < contentLength) {
// Start my starvation timer.
// This ensures that the other end
// writes at least some data in
// or we will close the pipe from
// him. This prevents DOS attack
// that takes up all our connections.
this.rawInputStream.startTimer();
try {
int readlength = inputStream.read(message_body,
nread, contentLength - nread);
if (readlength > 0) {
nread += readlength;
} else {
break;
}
} catch (IOException ex) {
stackLogger.logError("Exception Reading Content",ex);
break;
} finally {
// Stop my starvation timer.
this.rawInputStream.stopTimer();
}
}
sipMessage.setMessageContent(message_body);
}
// Content length too large - process the message and
// return error from there.
if (sipMessageListener != null) {
try {
if(postParseExecutor == null) {
/**
* If gov.nist.javax.sip.TCP_POST_PARSING_THREAD_POOL_SIZE is disabled
* we continue with the old logic here.
*/
if(sipStack.sipEventInterceptor != null) {
sipStack.sipEventInterceptor.beforeMessage(sipMessage);
}
sipMessageListener.processMessage(sipMessage);
if(sipStack.sipEventInterceptor != null) {
sipStack.sipEventInterceptor.afterMessage(sipMessage);
}
} else {
/**
* gov.nist.javax.sip.TCP_POST_PARSING_THREAD_POOL_SIZE is enabled so
* we use the threadpool to execute the task.
*/
// we need to guarantee message ordering on the same socket on TCP
// so we lock and queue of messages per Call Id
final String callId = sipMessage.getCallId().getCallId();
// http://dmy999.com/article/34/correct-use-of-concurrenthashmap
CallIDOrderingStructure orderingStructure = messagesOrderingMap.get(callId);
if(orderingStructure == null) {
CallIDOrderingStructure newCallIDOrderingStructure = new CallIDOrderingStructure();
orderingStructure = messagesOrderingMap.putIfAbsent(callId, newCallIDOrderingStructure);
if(orderingStructure == null) {
orderingStructure = newCallIDOrderingStructure;
if (stackLogger.isLoggingEnabled(StackLogger.TRACE_DEBUG)) {
stackLogger.logDebug("new CallIDOrderingStructure added for message " + sipMessage);
}
}
}
final CallIDOrderingStructure callIDOrderingStructure = orderingStructure;
// we add the message to the pending queue of messages to be processed for that call id here