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