1/*
2 * Conditions Of Use
3 *
4 * This software was developed by employees of the National Institute of
5 * Standards and Technology (NIST), an agency of the Federal Government.
6 * Pursuant to title 15 Untied States Code Section 105, works of NIST
7 * employees are not subject to copyright protection in the United States
8 * and are considered to be in the public domain.  As a result, a formal
9 * license is not needed to use the software.
10 *
11 * This software is provided by NIST as a service and is expressly
12 * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
13 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
14 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
15 * AND DATA ACCURACY.  NIST does not warrant or make any representations
16 * regarding the use of the software or the results thereof, including but
17 * not limited to the correctness, accuracy, reliability or usefulness of
18 * the software.
19 *
20 * Permission to use this software is contingent upon your acceptance
21 * of the terms of this agreement
22 *
23 * .
24 *
25 */
26package gov.nist.javax.sip;
27
28import java.util.*;
29import gov.nist.javax.sip.stack.*;
30import gov.nist.javax.sip.message.*;
31import javax.sip.message.*;
32import javax.sip.*;
33import gov.nist.core.ThreadAuditor;
34
35/* bug fixes SIPQuest communications and Shu-Lin Chen. */
36
37/**
38 * Event Scanner to deliver events to the Listener.
39 *
40 * @version 1.2 $Revision: 1.41 $ $Date: 2009/11/18 02:35:17 $
41 *
42 * @author M. Ranganathan <br/>
43 *
44 *
45 */
46class EventScanner implements Runnable {
47
48    private boolean isStopped;
49
50    private int refCount;
51
52    // SIPquest: Fix for deadlocks
53    private LinkedList pendingEvents = new LinkedList();
54
55    private int[] eventMutex = { 0 };
56
57    private SipStackImpl sipStack;
58
59    public void incrementRefcount() {
60        synchronized (eventMutex) {
61            this.refCount++;
62        }
63    }
64
65    public EventScanner(SipStackImpl sipStackImpl) {
66        this.pendingEvents = new LinkedList();
67        Thread myThread = new Thread(this);
68        // This needs to be set to false else the
69        // main thread mysteriously exits.
70        myThread.setDaemon(false);
71
72        this.sipStack = sipStackImpl;
73
74        myThread.setName("EventScannerThread");
75
76        myThread.start();
77
78    }
79
80    public void addEvent(EventWrapper eventWrapper) {
81    	if (sipStack.isLoggingEnabled())
82    		sipStack.getStackLogger().logDebug("addEvent " + eventWrapper);
83        synchronized (this.eventMutex) {
84
85            pendingEvents.add(eventWrapper);
86
87            // Add the event into the pending events list
88
89            eventMutex.notify();
90        }
91
92    }
93
94    /**
95     * Stop the event scanner. Decrement the reference count and exit the
96     * scanner thread if the ref count goes to 0.
97     */
98
99    public void stop() {
100        synchronized (eventMutex) {
101
102            if (this.refCount > 0)
103                this.refCount--;
104
105            if (this.refCount == 0) {
106                isStopped = true;
107                eventMutex.notify();
108
109            }
110        }
111    }
112
113    /**
114     * Brutally stop the event scanner. This does not wait for the refcount to
115     * go to 0.
116     *
117     */
118    public void forceStop() {
119        synchronized (this.eventMutex) {
120            this.isStopped = true;
121            this.refCount = 0;
122            this.eventMutex.notify();
123        }
124
125    }
126
127    public void deliverEvent(EventWrapper eventWrapper) {
128        EventObject sipEvent = eventWrapper.sipEvent;
129        if (sipStack.isLoggingEnabled())
130            sipStack.getStackLogger().logDebug(
131                    "sipEvent = " + sipEvent + "source = "
132                            + sipEvent.getSource());
133        SipListener sipListener = null;
134
135        if (!(sipEvent instanceof IOExceptionEvent)) {
136            sipListener = ((SipProviderImpl) sipEvent.getSource()).getSipListener();
137        } else {
138            sipListener = sipStack.getSipListener();
139        }
140
141        if (sipEvent instanceof RequestEvent) {
142            try {
143                // Check if this request has already created a
144                // transaction
145                SIPRequest sipRequest = (SIPRequest) ((RequestEvent) sipEvent)
146                        .getRequest();
147
148                if (sipStack.isLoggingEnabled()) {
149                    sipStack.getStackLogger().logDebug(
150                            "deliverEvent : "
151                                    + sipRequest.getFirstLine()
152                                    + " transaction "
153                                    + eventWrapper.transaction
154                                    + " sipEvent.serverTx = "
155                                    + ((RequestEvent) sipEvent)
156                                            .getServerTransaction());
157                }
158
159                // Discard the duplicate request if a
160                // transaction already exists. If the listener chose
161                // to handle the request statelessly, then the listener
162                // will see the retransmission.
163                // Note that in both of these two cases, JAIN SIP will allow
164                // you to handle the request statefully or statelessly.
165                // An example of the latter case is REGISTER and an example
166                // of the former case is INVITE.
167
168                SIPServerTransaction tx = (SIPServerTransaction) sipStack
169                        .findTransaction(sipRequest, true);
170
171                if (tx != null && !tx.passToListener()) {
172
173                    // JvB: make an exception for a very rare case: some
174                    // (broken) UACs use
175                    // the same branch parameter for an ACK. Such an ACK should
176                    // be passed
177                    // to the listener (tx == INVITE ST, terminated upon sending
178                    // 2xx but
179                    // lingering to catch retransmitted INVITEs)
180                    if (sipRequest.getMethod().equals(Request.ACK)
181                            && tx.isInviteTransaction() &&
182                            ( tx.getLastResponse().getStatusCode()/100 == 2 ||
183                                sipStack.isNon2XXAckPassedToListener())) {
184
185                        if (sipStack.isLoggingEnabled())
186                            sipStack
187                                    .getStackLogger()
188                                    .logDebug(
189                                            "Detected broken client sending ACK with same branch! Passing...");
190                    } else {
191                        if (sipStack.isLoggingEnabled())
192                            sipStack.getStackLogger().logDebug(
193                                    "transaction already exists! " + tx);
194                        return;
195                    }
196                } else if (sipStack.findPendingTransaction(sipRequest) != null) {
197                    if (sipStack.isLoggingEnabled())
198                        sipStack.getStackLogger().logDebug(
199                                "transaction already exists!!");
200
201                    return;
202                } else {
203                    // Put it in the pending list so that if a repeat
204                    // request comes along it will not get assigned a
205                    // new transaction
206                    SIPServerTransaction st = (SIPServerTransaction) eventWrapper.transaction;
207                    sipStack.putPendingTransaction(st);
208                }
209
210                // Set up a pointer to the transaction.
211                sipRequest.setTransaction(eventWrapper.transaction);
212                // Change made by SIPquest
213                try {
214
215                    if (sipStack.isLoggingEnabled()) {
216                        sipStack.getStackLogger()
217                                .logDebug(
218                                        "Calling listener "
219                                                + sipRequest.getFirstLine());
220                        sipStack.getStackLogger().logDebug(
221                                "Calling listener " + eventWrapper.transaction);
222                    }
223                    if (sipListener != null)
224                        sipListener.processRequest((RequestEvent) sipEvent);
225
226                    if (sipStack.isLoggingEnabled()) {
227                        sipStack.getStackLogger().logDebug(
228                                "Done processing Message "
229                                        + sipRequest.getFirstLine());
230                    }
231                    if (eventWrapper.transaction != null) {
232
233                        SIPDialog dialog = (SIPDialog) eventWrapper.transaction
234                                .getDialog();
235                        if (dialog != null)
236                            dialog.requestConsumed();
237
238                    }
239                } catch (Exception ex) {
240                    // We cannot let this thread die under any
241                    // circumstances. Protect ourselves by logging
242                    // errors to the console but continue.
243                    sipStack.getStackLogger().logException(ex);
244                }
245            } finally {
246                if (sipStack.isLoggingEnabled()) {
247                    sipStack.getStackLogger().logDebug(
248                            "Done processing Message "
249                                    + ((SIPRequest) (((RequestEvent) sipEvent)
250                                            .getRequest())).getFirstLine());
251                }
252                if (eventWrapper.transaction != null
253                        && ((SIPServerTransaction) eventWrapper.transaction)
254                                .passToListener()) {
255                    ((SIPServerTransaction) eventWrapper.transaction)
256                            .releaseSem();
257                }
258
259                if (eventWrapper.transaction != null)
260                    sipStack
261                            .removePendingTransaction((SIPServerTransaction) eventWrapper.transaction);
262                if (eventWrapper.transaction.getOriginalRequest().getMethod()
263                        .equals(Request.ACK)) {
264                    // Set the tx state to terminated so it is removed from the
265                    // stack
266                    // if the user configured to get notification on ACK
267                    // termination
268                    eventWrapper.transaction
269                            .setState(TransactionState.TERMINATED);
270                }
271            }
272
273        } else if (sipEvent instanceof ResponseEvent) {
274            try {
275                ResponseEvent responseEvent = (ResponseEvent) sipEvent;
276                SIPResponse sipResponse = (SIPResponse) responseEvent
277                        .getResponse();
278                SIPDialog sipDialog = ((SIPDialog) responseEvent.getDialog());
279                try {
280                    if (sipStack.isLoggingEnabled()) {
281
282                        sipStack.getStackLogger().logDebug(
283                                "Calling listener for "
284                                        + sipResponse.getFirstLine());
285                    }
286                    if (sipListener != null) {
287                        SIPTransaction tx = eventWrapper.transaction;
288                        if (tx != null) {
289                            tx.setPassToListener();
290                        }
291                        sipListener.processResponse((ResponseEvent) sipEvent);
292                    }
293
294                    /*
295                     * If the response for a request within a dialog is a 481
296                     * (Call/Transaction Does Not Exist) or a 408 (Request
297                     * Timeout), the UAC SHOULD terminate the dialog.
298                     */
299                    if ((sipDialog != null && (sipDialog.getState() == null || !sipDialog
300                            .getState().equals(DialogState.TERMINATED)))
301                            && (sipResponse.getStatusCode() == Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST || sipResponse
302                                    .getStatusCode() == Response.REQUEST_TIMEOUT)) {
303                        if (sipStack.isLoggingEnabled()) {
304                            sipStack.getStackLogger().logDebug(
305                                    "Removing dialog on 408 or 481 response");
306                        }
307                        sipDialog.doDeferredDelete();
308                    }
309
310                    /*
311                     * The Client tx disappears after the first 2xx response
312                     * However, additional 2xx responses may arrive later for
313                     * example in the following scenario:
314                     *
315                     * Multiple 2xx responses may arrive at the UAC for a single
316                     * INVITE request due to a forking proxy. Each response is
317                     * distinguished by the tag parameter in the To header
318                     * field, and each represents a distinct dialog, with a
319                     * distinct dialog identifier.
320                     *
321                     * If the Listener does not ACK the 200 then we assume he
322                     * does not care about the dialog and gc the dialog after
323                     * some time. However, this is really an application bug.
324                     * This garbage collects unacknowledged dialogs.
325                     *
326                     */
327                    if (sipResponse.getCSeq().getMethod()
328                            .equals(Request.INVITE)
329                            && sipDialog != null
330                            && sipResponse.getStatusCode() == 200) {
331                        if (sipStack.isLoggingEnabled()) {
332                            sipStack.getStackLogger().logDebug(
333                                    "Warning! unacknowledged dialog. " + sipDialog.getState());
334                        }
335                        /*
336                         * If we dont see an ACK in 32 seconds, we want to tear down the dialog.
337                         */
338                        sipDialog.doDeferredDeleteIfNoAckSent(sipResponse.getCSeq().getSeqNumber());
339                    }
340                } catch (Exception ex) {
341                    // We cannot let this thread die under any
342                    // circumstances. Protect ourselves by logging
343                    // errors to the console but continue.
344                    sipStack.getStackLogger().logException(ex);
345                }
346                // The original request is not needed except for INVITE
347                // transactions -- null the pointers to the transactions so
348                // that state may be released.
349                SIPClientTransaction ct = (SIPClientTransaction) eventWrapper.transaction;
350                if (ct != null
351                        && TransactionState.COMPLETED == ct.getState()
352                        && ct.getOriginalRequest() != null
353                        && !ct.getOriginalRequest().getMethod().equals(
354                                Request.INVITE)) {
355                    // reduce the state to minimum
356                    // This assumes that the application will not need
357                    // to access the request once the transaction is
358                    // completed.
359                    ct.clearState();
360                }
361                // mark no longer in the event queue.
362            } finally {
363                if (eventWrapper.transaction != null
364                        && eventWrapper.transaction.passToListener()) {
365                    eventWrapper.transaction.releaseSem();
366                }
367            }
368
369        } else if (sipEvent instanceof TimeoutEvent) {
370            // Change made by SIPquest
371            try {
372                // Check for null as listener could be removed.
373                if (sipListener != null)
374                    sipListener.processTimeout((TimeoutEvent) sipEvent);
375            } catch (Exception ex) {
376                // We cannot let this thread die under any
377                // circumstances. Protect ourselves by logging
378                // errors to the console but continue.
379                sipStack.getStackLogger().logException(ex);
380            }
381
382        } else if (sipEvent instanceof DialogTimeoutEvent) {
383            try {
384                // Check for null as listener could be removed.
385                if (sipListener != null && sipListener instanceof SipListenerExt) {
386                    ((SipListenerExt)sipListener).processDialogTimeout((DialogTimeoutEvent) sipEvent);
387                }
388            } catch (Exception ex) {
389                // We cannot let this thread die under any
390                // circumstances. Protect ourselves by logging
391                // errors to the console but continue.
392                sipStack.getStackLogger().logException(ex);
393            }
394
395        } else if (sipEvent instanceof IOExceptionEvent) {
396            try {
397                if (sipListener != null)
398                    sipListener.processIOException((IOExceptionEvent) sipEvent);
399            } catch (Exception ex) {
400                sipStack.getStackLogger().logException(ex);
401            }
402        } else if (sipEvent instanceof TransactionTerminatedEvent) {
403            try {
404                if (sipStack.isLoggingEnabled()) {
405                    sipStack.getStackLogger().logDebug(
406                            "About to deliver transactionTerminatedEvent");
407                    sipStack.getStackLogger().logDebug(
408                            "tx = "
409                                    + ((TransactionTerminatedEvent) sipEvent)
410                                            .getClientTransaction());
411                    sipStack.getStackLogger().logDebug(
412                            "tx = "
413                                    + ((TransactionTerminatedEvent) sipEvent)
414                                            .getServerTransaction());
415
416                }
417                if (sipListener != null)
418                    sipListener
419                            .processTransactionTerminated((TransactionTerminatedEvent) sipEvent);
420            } catch (AbstractMethodError ame) {
421                // JvB: for backwards compatibility, accept this
422            	if (sipStack.isLoggingEnabled())
423            		sipStack
424                        .getStackLogger()
425                        .logWarning(
426                                "Unable to call sipListener.processTransactionTerminated");
427            } catch (Exception ex) {
428                sipStack.getStackLogger().logException(ex);
429            }
430        } else if (sipEvent instanceof DialogTerminatedEvent) {
431            try {
432                if (sipListener != null)
433                    sipListener
434                            .processDialogTerminated((DialogTerminatedEvent) sipEvent);
435            } catch (AbstractMethodError ame) {
436                // JvB: for backwards compatibility, accept this
437            	if (sipStack.isLoggingEnabled())
438            		sipStack.getStackLogger().logWarning(
439                        "Unable to call sipListener.processDialogTerminated");
440            } catch (Exception ex) {
441                sipStack.getStackLogger().logException(ex);
442            }
443        } else {
444
445            sipStack.getStackLogger().logFatalError("bad event" + sipEvent);
446        }
447
448    }
449
450    /**
451     * For the non-re-entrant listener this delivers the events to the listener
452     * from a single queue. If the listener is re-entrant, then the stack just
453     * calls the deliverEvent method above.
454     */
455
456    public void run() {
457        try {
458            // Ask the auditor to monitor this thread
459            ThreadAuditor.ThreadHandle threadHandle = sipStack.getThreadAuditor().addCurrentThread();
460
461            while (true) {
462                EventWrapper eventWrapper = null;
463
464                LinkedList eventsToDeliver;
465                synchronized (this.eventMutex) {
466                    // First, wait for some events to become available.
467                    while (pendingEvents.isEmpty()) {
468                        // There's nothing in the list, check to make sure we
469                        // haven't
470                        // been stopped. If we have, then let the thread die.
471                        if (this.isStopped) {
472                            if (sipStack.isLoggingEnabled())
473                                sipStack.getStackLogger().logDebug(
474                                        "Stopped event scanner!!");
475                            return;
476                        }
477
478                        // We haven't been stopped, and the event list is indeed
479                        // rather empty. Wait for some events to come along.
480                        try {
481                            // Send a heartbeat to the thread auditor
482                            threadHandle.ping();
483
484                            // Wait for events (with a timeout)
485                            eventMutex.wait(threadHandle.getPingIntervalInMillisecs());
486                        } catch (InterruptedException ex) {
487                            // Let the thread die a normal death
488                        	if (sipStack.isLoggingEnabled())
489                        		sipStack.getStackLogger().logDebug("Interrupted!");
490                            return;
491                        }
492                    }
493
494                    // There are events in the 'pending events list' that need
495                    // processing. Hold onto the old 'pending Events' list, but
496                    // make a new one for the other methods to operate on. This
497                    // tap-dancing is to avoid deadlocks and also to ensure that
498                    // the list is not modified while we are iterating over it.
499                    eventsToDeliver = pendingEvents;
500                    pendingEvents = new LinkedList();
501                }
502                ListIterator iterator = eventsToDeliver.listIterator();
503                while (iterator.hasNext()) {
504                    eventWrapper = (EventWrapper) iterator.next();
505                    if (sipStack.isLoggingEnabled()) {
506                        sipStack.getStackLogger().logDebug(
507                                "Processing " + eventWrapper + "nevents "
508                                        + eventsToDeliver.size());
509                    }
510                    try {
511                        deliverEvent(eventWrapper);
512                    } catch (Exception e) {
513                        if (sipStack.isLoggingEnabled()) {
514                            sipStack.getStackLogger().logError(
515                                    "Unexpected exception caught while delivering event -- carrying on bravely", e);
516                        }
517                    }
518                }
519            } // end While
520        } finally {
521            if (sipStack.isLoggingEnabled()) {
522                if (!this.isStopped) {
523                    sipStack.getStackLogger().logFatalError("Event scanner exited abnormally");
524                }
525            }
526        }
527    }
528
529}
530