You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
530 lines
22 KiB
530 lines
22 KiB
/*
|
|
* 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 Untied 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
|
|
*
|
|
* .
|
|
*
|
|
*/
|
|
package gov.nist.javax.sip;
|
|
|
|
import java.util.*;
|
|
import gov.nist.javax.sip.stack.*;
|
|
import gov.nist.javax.sip.message.*;
|
|
import javax.sip.message.*;
|
|
import javax.sip.*;
|
|
import gov.nist.core.ThreadAuditor;
|
|
|
|
/* bug fixes SIPQuest communications and Shu-Lin Chen. */
|
|
|
|
/**
|
|
* Event Scanner to deliver events to the Listener.
|
|
*
|
|
* @version 1.2 $Revision: 1.41 $ $Date: 2009/11/18 02:35:17 $
|
|
*
|
|
* @author M. Ranganathan <br/>
|
|
*
|
|
*
|
|
*/
|
|
class EventScanner implements Runnable {
|
|
|
|
private boolean isStopped;
|
|
|
|
private int refCount;
|
|
|
|
// SIPquest: Fix for deadlocks
|
|
private LinkedList pendingEvents = new LinkedList();
|
|
|
|
private int[] eventMutex = { 0 };
|
|
|
|
private SipStackImpl sipStack;
|
|
|
|
public void incrementRefcount() {
|
|
synchronized (eventMutex) {
|
|
this.refCount++;
|
|
}
|
|
}
|
|
|
|
public EventScanner(SipStackImpl sipStackImpl) {
|
|
this.pendingEvents = new LinkedList();
|
|
Thread myThread = new Thread(this);
|
|
// This needs to be set to false else the
|
|
// main thread mysteriously exits.
|
|
myThread.setDaemon(false);
|
|
|
|
this.sipStack = sipStackImpl;
|
|
|
|
myThread.setName("EventScannerThread");
|
|
|
|
myThread.start();
|
|
|
|
}
|
|
|
|
public void addEvent(EventWrapper eventWrapper) {
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger().logDebug("addEvent " + eventWrapper);
|
|
synchronized (this.eventMutex) {
|
|
|
|
pendingEvents.add(eventWrapper);
|
|
|
|
// Add the event into the pending events list
|
|
|
|
eventMutex.notify();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Stop the event scanner. Decrement the reference count and exit the
|
|
* scanner thread if the ref count goes to 0.
|
|
*/
|
|
|
|
public void stop() {
|
|
synchronized (eventMutex) {
|
|
|
|
if (this.refCount > 0)
|
|
this.refCount--;
|
|
|
|
if (this.refCount == 0) {
|
|
isStopped = true;
|
|
eventMutex.notify();
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Brutally stop the event scanner. This does not wait for the refcount to
|
|
* go to 0.
|
|
*
|
|
*/
|
|
public void forceStop() {
|
|
synchronized (this.eventMutex) {
|
|
this.isStopped = true;
|
|
this.refCount = 0;
|
|
this.eventMutex.notify();
|
|
}
|
|
|
|
}
|
|
|
|
public void deliverEvent(EventWrapper eventWrapper) {
|
|
EventObject sipEvent = eventWrapper.sipEvent;
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger().logDebug(
|
|
"sipEvent = " + sipEvent + "source = "
|
|
+ sipEvent.getSource());
|
|
SipListener sipListener = null;
|
|
|
|
if (!(sipEvent instanceof IOExceptionEvent)) {
|
|
sipListener = ((SipProviderImpl) sipEvent.getSource()).getSipListener();
|
|
} else {
|
|
sipListener = sipStack.getSipListener();
|
|
}
|
|
|
|
if (sipEvent instanceof RequestEvent) {
|
|
try {
|
|
// Check if this request has already created a
|
|
// transaction
|
|
SIPRequest sipRequest = (SIPRequest) ((RequestEvent) sipEvent)
|
|
.getRequest();
|
|
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logDebug(
|
|
"deliverEvent : "
|
|
+ sipRequest.getFirstLine()
|
|
+ " transaction "
|
|
+ eventWrapper.transaction
|
|
+ " sipEvent.serverTx = "
|
|
+ ((RequestEvent) sipEvent)
|
|
.getServerTransaction());
|
|
}
|
|
|
|
// Discard the duplicate request if a
|
|
// transaction already exists. If the listener chose
|
|
// to handle the request statelessly, then the listener
|
|
// will see the retransmission.
|
|
// Note that in both of these two cases, JAIN SIP will allow
|
|
// you to handle the request statefully or statelessly.
|
|
// An example of the latter case is REGISTER and an example
|
|
// of the former case is INVITE.
|
|
|
|
SIPServerTransaction tx = (SIPServerTransaction) sipStack
|
|
.findTransaction(sipRequest, true);
|
|
|
|
if (tx != null && !tx.passToListener()) {
|
|
|
|
// JvB: make an exception for a very rare case: some
|
|
// (broken) UACs use
|
|
// the same branch parameter for an ACK. Such an ACK should
|
|
// be passed
|
|
// to the listener (tx == INVITE ST, terminated upon sending
|
|
// 2xx but
|
|
// lingering to catch retransmitted INVITEs)
|
|
if (sipRequest.getMethod().equals(Request.ACK)
|
|
&& tx.isInviteTransaction() &&
|
|
( tx.getLastResponse().getStatusCode()/100 == 2 ||
|
|
sipStack.isNon2XXAckPassedToListener())) {
|
|
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack
|
|
.getStackLogger()
|
|
.logDebug(
|
|
"Detected broken client sending ACK with same branch! Passing...");
|
|
} else {
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger().logDebug(
|
|
"transaction already exists! " + tx);
|
|
return;
|
|
}
|
|
} else if (sipStack.findPendingTransaction(sipRequest) != null) {
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger().logDebug(
|
|
"transaction already exists!!");
|
|
|
|
return;
|
|
} else {
|
|
// Put it in the pending list so that if a repeat
|
|
// request comes along it will not get assigned a
|
|
// new transaction
|
|
SIPServerTransaction st = (SIPServerTransaction) eventWrapper.transaction;
|
|
sipStack.putPendingTransaction(st);
|
|
}
|
|
|
|
// Set up a pointer to the transaction.
|
|
sipRequest.setTransaction(eventWrapper.transaction);
|
|
// Change made by SIPquest
|
|
try {
|
|
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger()
|
|
.logDebug(
|
|
"Calling listener "
|
|
+ sipRequest.getFirstLine());
|
|
sipStack.getStackLogger().logDebug(
|
|
"Calling listener " + eventWrapper.transaction);
|
|
}
|
|
if (sipListener != null)
|
|
sipListener.processRequest((RequestEvent) sipEvent);
|
|
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logDebug(
|
|
"Done processing Message "
|
|
+ sipRequest.getFirstLine());
|
|
}
|
|
if (eventWrapper.transaction != null) {
|
|
|
|
SIPDialog dialog = (SIPDialog) eventWrapper.transaction
|
|
.getDialog();
|
|
if (dialog != null)
|
|
dialog.requestConsumed();
|
|
|
|
}
|
|
} catch (Exception ex) {
|
|
// We cannot let this thread die under any
|
|
// circumstances. Protect ourselves by logging
|
|
// errors to the console but continue.
|
|
sipStack.getStackLogger().logException(ex);
|
|
}
|
|
} finally {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logDebug(
|
|
"Done processing Message "
|
|
+ ((SIPRequest) (((RequestEvent) sipEvent)
|
|
.getRequest())).getFirstLine());
|
|
}
|
|
if (eventWrapper.transaction != null
|
|
&& ((SIPServerTransaction) eventWrapper.transaction)
|
|
.passToListener()) {
|
|
((SIPServerTransaction) eventWrapper.transaction)
|
|
.releaseSem();
|
|
}
|
|
|
|
if (eventWrapper.transaction != null)
|
|
sipStack
|
|
.removePendingTransaction((SIPServerTransaction) eventWrapper.transaction);
|
|
if (eventWrapper.transaction.getOriginalRequest().getMethod()
|
|
.equals(Request.ACK)) {
|
|
// Set the tx state to terminated so it is removed from the
|
|
// stack
|
|
// if the user configured to get notification on ACK
|
|
// termination
|
|
eventWrapper.transaction
|
|
.setState(TransactionState.TERMINATED);
|
|
}
|
|
}
|
|
|
|
} else if (sipEvent instanceof ResponseEvent) {
|
|
try {
|
|
ResponseEvent responseEvent = (ResponseEvent) sipEvent;
|
|
SIPResponse sipResponse = (SIPResponse) responseEvent
|
|
.getResponse();
|
|
SIPDialog sipDialog = ((SIPDialog) responseEvent.getDialog());
|
|
try {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
|
|
sipStack.getStackLogger().logDebug(
|
|
"Calling listener for "
|
|
+ sipResponse.getFirstLine());
|
|
}
|
|
if (sipListener != null) {
|
|
SIPTransaction tx = eventWrapper.transaction;
|
|
if (tx != null) {
|
|
tx.setPassToListener();
|
|
}
|
|
sipListener.processResponse((ResponseEvent) sipEvent);
|
|
}
|
|
|
|
/*
|
|
* If the response for a request within a dialog is a 481
|
|
* (Call/Transaction Does Not Exist) or a 408 (Request
|
|
* Timeout), the UAC SHOULD terminate the dialog.
|
|
*/
|
|
if ((sipDialog != null && (sipDialog.getState() == null || !sipDialog
|
|
.getState().equals(DialogState.TERMINATED)))
|
|
&& (sipResponse.getStatusCode() == Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST || sipResponse
|
|
.getStatusCode() == Response.REQUEST_TIMEOUT)) {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logDebug(
|
|
"Removing dialog on 408 or 481 response");
|
|
}
|
|
sipDialog.doDeferredDelete();
|
|
}
|
|
|
|
/*
|
|
* The Client tx disappears after the first 2xx response
|
|
* However, additional 2xx responses may arrive later for
|
|
* example in the following scenario:
|
|
*
|
|
* Multiple 2xx responses may arrive at the UAC for a single
|
|
* INVITE request due to a forking proxy. Each response is
|
|
* distinguished by the tag parameter in the To header
|
|
* field, and each represents a distinct dialog, with a
|
|
* distinct dialog identifier.
|
|
*
|
|
* If the Listener does not ACK the 200 then we assume he
|
|
* does not care about the dialog and gc the dialog after
|
|
* some time. However, this is really an application bug.
|
|
* This garbage collects unacknowledged dialogs.
|
|
*
|
|
*/
|
|
if (sipResponse.getCSeq().getMethod()
|
|
.equals(Request.INVITE)
|
|
&& sipDialog != null
|
|
&& sipResponse.getStatusCode() == 200) {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logDebug(
|
|
"Warning! unacknowledged dialog. " + sipDialog.getState());
|
|
}
|
|
/*
|
|
* If we dont see an ACK in 32 seconds, we want to tear down the dialog.
|
|
*/
|
|
sipDialog.doDeferredDeleteIfNoAckSent(sipResponse.getCSeq().getSeqNumber());
|
|
}
|
|
} catch (Exception ex) {
|
|
// We cannot let this thread die under any
|
|
// circumstances. Protect ourselves by logging
|
|
// errors to the console but continue.
|
|
sipStack.getStackLogger().logException(ex);
|
|
}
|
|
// The original request is not needed except for INVITE
|
|
// transactions -- null the pointers to the transactions so
|
|
// that state may be released.
|
|
SIPClientTransaction ct = (SIPClientTransaction) eventWrapper.transaction;
|
|
if (ct != null
|
|
&& TransactionState.COMPLETED == ct.getState()
|
|
&& ct.getOriginalRequest() != null
|
|
&& !ct.getOriginalRequest().getMethod().equals(
|
|
Request.INVITE)) {
|
|
// reduce the state to minimum
|
|
// This assumes that the application will not need
|
|
// to access the request once the transaction is
|
|
// completed.
|
|
ct.clearState();
|
|
}
|
|
// mark no longer in the event queue.
|
|
} finally {
|
|
if (eventWrapper.transaction != null
|
|
&& eventWrapper.transaction.passToListener()) {
|
|
eventWrapper.transaction.releaseSem();
|
|
}
|
|
}
|
|
|
|
} else if (sipEvent instanceof TimeoutEvent) {
|
|
// Change made by SIPquest
|
|
try {
|
|
// Check for null as listener could be removed.
|
|
if (sipListener != null)
|
|
sipListener.processTimeout((TimeoutEvent) sipEvent);
|
|
} catch (Exception ex) {
|
|
// We cannot let this thread die under any
|
|
// circumstances. Protect ourselves by logging
|
|
// errors to the console but continue.
|
|
sipStack.getStackLogger().logException(ex);
|
|
}
|
|
|
|
} else if (sipEvent instanceof DialogTimeoutEvent) {
|
|
try {
|
|
// Check for null as listener could be removed.
|
|
if (sipListener != null && sipListener instanceof SipListenerExt) {
|
|
((SipListenerExt)sipListener).processDialogTimeout((DialogTimeoutEvent) sipEvent);
|
|
}
|
|
} catch (Exception ex) {
|
|
// We cannot let this thread die under any
|
|
// circumstances. Protect ourselves by logging
|
|
// errors to the console but continue.
|
|
sipStack.getStackLogger().logException(ex);
|
|
}
|
|
|
|
} else if (sipEvent instanceof IOExceptionEvent) {
|
|
try {
|
|
if (sipListener != null)
|
|
sipListener.processIOException((IOExceptionEvent) sipEvent);
|
|
} catch (Exception ex) {
|
|
sipStack.getStackLogger().logException(ex);
|
|
}
|
|
} else if (sipEvent instanceof TransactionTerminatedEvent) {
|
|
try {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logDebug(
|
|
"About to deliver transactionTerminatedEvent");
|
|
sipStack.getStackLogger().logDebug(
|
|
"tx = "
|
|
+ ((TransactionTerminatedEvent) sipEvent)
|
|
.getClientTransaction());
|
|
sipStack.getStackLogger().logDebug(
|
|
"tx = "
|
|
+ ((TransactionTerminatedEvent) sipEvent)
|
|
.getServerTransaction());
|
|
|
|
}
|
|
if (sipListener != null)
|
|
sipListener
|
|
.processTransactionTerminated((TransactionTerminatedEvent) sipEvent);
|
|
} catch (AbstractMethodError ame) {
|
|
// JvB: for backwards compatibility, accept this
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack
|
|
.getStackLogger()
|
|
.logWarning(
|
|
"Unable to call sipListener.processTransactionTerminated");
|
|
} catch (Exception ex) {
|
|
sipStack.getStackLogger().logException(ex);
|
|
}
|
|
} else if (sipEvent instanceof DialogTerminatedEvent) {
|
|
try {
|
|
if (sipListener != null)
|
|
sipListener
|
|
.processDialogTerminated((DialogTerminatedEvent) sipEvent);
|
|
} catch (AbstractMethodError ame) {
|
|
// JvB: for backwards compatibility, accept this
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger().logWarning(
|
|
"Unable to call sipListener.processDialogTerminated");
|
|
} catch (Exception ex) {
|
|
sipStack.getStackLogger().logException(ex);
|
|
}
|
|
} else {
|
|
|
|
sipStack.getStackLogger().logFatalError("bad event" + sipEvent);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* For the non-re-entrant listener this delivers the events to the listener
|
|
* from a single queue. If the listener is re-entrant, then the stack just
|
|
* calls the deliverEvent method above.
|
|
*/
|
|
|
|
public void run() {
|
|
try {
|
|
// Ask the auditor to monitor this thread
|
|
ThreadAuditor.ThreadHandle threadHandle = sipStack.getThreadAuditor().addCurrentThread();
|
|
|
|
while (true) {
|
|
EventWrapper eventWrapper = null;
|
|
|
|
LinkedList eventsToDeliver;
|
|
synchronized (this.eventMutex) {
|
|
// First, wait for some events to become available.
|
|
while (pendingEvents.isEmpty()) {
|
|
// There's nothing in the list, check to make sure we
|
|
// haven't
|
|
// been stopped. If we have, then let the thread die.
|
|
if (this.isStopped) {
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger().logDebug(
|
|
"Stopped event scanner!!");
|
|
return;
|
|
}
|
|
|
|
// We haven't been stopped, and the event list is indeed
|
|
// rather empty. Wait for some events to come along.
|
|
try {
|
|
// Send a heartbeat to the thread auditor
|
|
threadHandle.ping();
|
|
|
|
// Wait for events (with a timeout)
|
|
eventMutex.wait(threadHandle.getPingIntervalInMillisecs());
|
|
} catch (InterruptedException ex) {
|
|
// Let the thread die a normal death
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger().logDebug("Interrupted!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// There are events in the 'pending events list' that need
|
|
// processing. Hold onto the old 'pending Events' list, but
|
|
// make a new one for the other methods to operate on. This
|
|
// tap-dancing is to avoid deadlocks and also to ensure that
|
|
// the list is not modified while we are iterating over it.
|
|
eventsToDeliver = pendingEvents;
|
|
pendingEvents = new LinkedList();
|
|
}
|
|
ListIterator iterator = eventsToDeliver.listIterator();
|
|
while (iterator.hasNext()) {
|
|
eventWrapper = (EventWrapper) iterator.next();
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logDebug(
|
|
"Processing " + eventWrapper + "nevents "
|
|
+ eventsToDeliver.size());
|
|
}
|
|
try {
|
|
deliverEvent(eventWrapper);
|
|
} catch (Exception e) {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logError(
|
|
"Unexpected exception caught while delivering event -- carrying on bravely", e);
|
|
}
|
|
}
|
|
}
|
|
} // end While
|
|
} finally {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
if (!this.isStopped) {
|
|
sipStack.getStackLogger().logFatalError("Event scanner exited abnormally");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|