package test.tck.msgflow; import java.util.TooManyListenersException; import javax.sip.*; import javax.sip.header.ContactHeader; import javax.sip.header.ToHeader; import javax.sip.message.Request; import javax.sip.message.Response; import org.apache.log4j.Logger; import junit.framework.Test; import junit.framework.TestSuite; import test.tck.TckInternalError; import test.tck.TiUnexpectedError; /** * The test tries to verify that Invite Client Transactions correctly change * states as specified by the rfc3261. The Tested Implementation is used * to send requests and the ReferenceImplementation issues (or not) corresponding * responses. ClientTransaction states are constantly queried * and compared to those in the state machine described in * section 17.1.1 of rfc3261 *
 *
 *                               |INVITE from TU
 *             Timer A fires     |INVITE sent
 *             Reset A,          V                      Timer B fires
 *             INVITE sent +-----------+                or Transport Err.
 *               +---------|           |---------------+inform TU
 *               |         |  Calling  |               |
 *               +-------->|           |-------------->|
 *                         +-----------+ 2xx           |
 *                            |  |       2xx to TU     |
 *                            |  |1xx                  |
 *    300-699 +---------------+  |1xx to TU            |
 *   ACK sent |                  |                     |
 *resp. to TU |  1xx             V                     |
 *            |  1xx to TU  -----------+               |
 *            |  +---------|           |               |
 *            |  |         |Proceeding |-------------->|
 *            |  +-------->|           | 2xx           |
 *            |            +-----------+ 2xx to TU     |
 *            |       300-699    |                     |
 *            |       ACK sent,  |                     |
 *            |       resp. to TU|                     |
 *            |                  |                     |      NOTE:
 *            |  300-699         V                     |
 *            |  ACK sent  +-----------+Transport Err. |  transitions
 *            |  +---------|           |Inform TU      |  labeled with
 *            |  |         | Completed |-------------->|  the event
 *            |  +-------->|           |               |  over the action
 *            |            +-----------+               |  to take
 *            |              ^   |                     |
 *            |              |   | Timer D fires       |
 *            +--------------+   | -                   |
 *                               |                     |
 *                               V                     |
 *                         +-----------+               |
 *                         |           |               |
 *                         | Terminated|<--------------+
 *                         |           |
 *                         +-----------+
 *
 *                 Figure 5: INVITE client transaction
 *
 * 
* * TODO Currently, transactions' behaviour is not tested for misappropriation of * incoming messages. * * TODO Currently, invite transactions are not tested for proper termination * upon respective timeouts. * * @author Emil Ivov * Network Research Team, Louis Pasteur University, Strasbourg, France. * This code is in the public domain. * @version 1.0 */ public class InviteClientTransactionsStateMachineTest extends MessageFlowHarness { private static Logger logger = Logger.getLogger(InviteClientTransactionsStateMachineTest.class); public InviteClientTransactionsStateMachineTest(String name) { super(name,false); // disable auto-dialog for the RI, else ACKs get filtered out } //==================== tests ============================== /** * Tries to steer a TI client transaction through the following scenario * Calling-->Proceeding-->Completed-->Terminated. Apart from state * transitions, we also test, retransmissions and proper hiding/passing * of messages to the TU. */ public void testCallingProceedingCompletedTerminatedScenario() { try { Request invite = createTiInviteRequest(null, null, null); ClientTransaction tran = null; try { eventCollector.collectRequestEvent(riSipProvider); tran = tiSipProvider.getNewClientTransaction(invite); tran.sendRequest(); } catch(SipException ex) { throw new TiUnexpectedError( "A SipExceptionOccurred while trying to send request!", ex); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } waitForMessage(); RequestEvent inviteReceivedEvent = eventCollector. extractCollectedRequestEvent(); if(inviteReceivedEvent==null ||inviteReceivedEvent.getRequest()==null) throw new TiUnexpectedError( "The invite request was not received by the RI!"); //At this point the ClientTransaction should be CALLING! assertEquals(TransactionState.CALLING, tran.getState()); //Check Request retransmission try { eventCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } //Wait for the retransmission timer to fire if it had not already done so. if(tran.getRetransmitTimer()>MESSAGES_ARRIVE_FOR) sleep( (long)tran.getRetransmitTimer() -MESSAGES_ARRIVE_FOR); //subtract the time we waited for the invite //Wait for the retransmitted request to arrive waitForMessage(); inviteReceivedEvent = eventCollector.extractCollectedRequestEvent(); assertNotNull("The invite request was not retransmitted!", inviteReceivedEvent); assertNotNull("The invite request was not retransmitted!", inviteReceivedEvent.getRequest()); assertEquals(Request.INVITE, inviteReceivedEvent.getRequest().getMethod()); //At this point the ClientTransaction should STILL be CALLING! assertEquals(TransactionState.CALLING, tran.getState()); //Send a TRYING response try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } try { Response resp = riMessageFactory.createResponse( Response.TRYING, inviteReceivedEvent.getRequest()); addStatus(inviteReceivedEvent.getRequest(),resp); riSipProvider.sendResponse(resp); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send a trying response back to the TI", ex); } waitForMessage(); //Analyze the TRYING response and Tran state back at the TI ResponseEvent responseEvent = eventCollector.extractCollectedResponseEvent(); assertNotNull( "The Tested Implementation did not pass a 1xx response to the TU!", responseEvent); assertNotNull( "The Tested Implementation did not pass a 1xx response to the TU!", responseEvent.getResponse()); assertTrue( "A response different from TYING was passed to the TU!", responseEvent.getResponse().getStatusCode()==Response.TRYING); assertSame( "The TRYING response was not associated with the right transaction.", tran, responseEvent.getClientTransaction()); //verify the the tran state is now PROCEEDING assertEquals( "The ClientTransaction did not pass in the PROCEEDING state after " +"receiving 1xx provisional response", tran.getState(), TransactionState.PROCEEDING ); //Send a 486 BUSY HERE (final) response from the RI try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } //The BUSY_HERE response should trigger some ACKs so let's register //a listener with the RI SipEventCollector ackCollector = new SipEventCollector(); try { ackCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } Response busyHere = null; try { busyHere = riMessageFactory.createResponse( Response.BUSY_HERE, inviteReceivedEvent.getRequest()); addStatus(inviteReceivedEvent.getRequest(),busyHere); // JvB: set to-tag too, mandatory and tests that ACK is properly formatted ((ToHeader) busyHere.getHeader("to")).setTag( "busy-here" ); riSipProvider.sendResponse(busyHere); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send a BUSY HERE response back to the TI", ex); } waitForMessage(); //Analyze the BUSY_HERE response and Tran state back at the TI responseEvent = eventCollector.extractCollectedResponseEvent(); assertNotNull( "The Tested Implementation did not pass a 300-699 response to the TU!", responseEvent); assertNotNull( "The Tested Implementation did not pass a 300-699 response to the TU!", responseEvent.getResponse()); assertSame( "The BUSY_HERE response was not associated with the right transaction", tran, responseEvent.getClientTransaction()); assertEquals( "A response different from BUSY_HERE was passed to the TU", Response.BUSY_HERE, responseEvent.getResponse().getStatusCode() ); assertEquals( "The ClientTransaction did not pass in the COMPLETED state after " +"receiving 300-699 final response", tran.getState(), TransactionState.COMPLETED ); //check whether the ackCollector has caught any fish RequestEvent ackReceivedEvent = ackCollector. extractCollectedRequestEvent(); // JvB: With auto-dialog-support enabled, the ACK should be filtered by the RI assertNotNull("The TI did not send an ACK request", ackReceivedEvent); assertNotNull("The TI did not send an ACK request", ackReceivedEvent.getRequest()); assertEquals(Request.ACK, ackReceivedEvent.getRequest().getMethod()); //Try to kill remaining ACK retransmissions //TODO this may not always work .. should give it a specific timeout value waitForMessage(); //Now let's retransmit the final response. This time it shouldn't be //passed to the TU but an ACK should still be sent try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } //go fish the ack try { ackCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } try { riSipProvider.sendResponse((Response)busyHere.clone()); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send a BUSY HERE response back to the TI", ex); } waitForMessage(); //The TU shouldn't see the retransmitted BUSY_HERE response responseEvent = eventCollector.extractCollectedResponseEvent(); assertNull( "The Tested Implementation passed a retransmitted 300-699 response " +"to the TU instead of just silently acknowledging it!", responseEvent); //We must still be in the completed state. assertEquals( "The ClientTransaction did not stay long enough in the COMPLETED " +"state.", tran.getState(), TransactionState.COMPLETED ); //check whether the ackCollector has caught any fish ackReceivedEvent = ackCollector.extractCollectedRequestEvent(); assertNotNull( "The TI did not send an ACK request to the second response", ackReceivedEvent); assertNotNull( "The TI did not send an ACK request to the second response", ackReceivedEvent.getRequest()); assertEquals(Request.ACK, ackReceivedEvent.getRequest().getMethod()); } catch(Throwable exc) { exc.printStackTrace(); fail(exc.getClass().getName()+": "+exc.getMessage()); } assertTrue(new Exception().getStackTrace()[0].toString(), true); //Unfortunately we can't assert the TERMINATED state as TIMER_K TIMER_D //is not exported by JAIN SIP } /** * JvB: Tests transmission of an INVITE followed by CANCELlation of that request * * -> INVITE * <- 100 * -> CANCEL * <- OK * <- 487 * -> ACK */ public void testInviteCancel() { doCancelTest(true); } /** * Same as above, but for non-RFC3261 client (i.e. no Via branch, no from/to tags * * Cannot do this on 1.2 stack * public void testInviteCancelNonRFC3261() { doCancelTest(false); } */ /** * JvB: Tests transmission of an INVITE followed by CANCELlation of that request * * -> INVITE * <- 100 * -> CANCEL * <- OK * <- 487 * -> ACK * * Note: for 1.2 it is impossible to manually set the top via branch to something * not RFC3261-compliant */ private void doCancelTest( boolean rfc3261Compliant ) { try { Request invite = createTiInviteRequest(null, null, null); ClientTransaction tran = null; try { eventCollector.collectRequestEvent(riSipProvider); // This call overwrites any branch we set tran = tiSipProvider.getNewClientTransaction(invite); // And this call too tran.sendRequest(); } catch(SipException ex) { throw new TiUnexpectedError( "A SipExceptionOccurred while trying to send request!", ex); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } waitForMessage(); RequestEvent inviteReceivedEvent = eventCollector.extractCollectedRequestEvent(); if(inviteReceivedEvent==null||inviteReceivedEvent.getRequest()==null) throw new TiUnexpectedError( "The invite request was not received by the RI!"); //At this point the ClientTransaction should be CALLING! assertEquals(TransactionState.CALLING, tran.getState()); //Send a TRYING response try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } try { Response resp = riMessageFactory.createResponse( Response.TRYING, inviteReceivedEvent.getRequest()); addStatus(inviteReceivedEvent.getRequest(),resp); riSipProvider.sendResponse(resp); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send a trying response back to the TI", ex); } waitForMessage(); //Analyze the TRYING response and Tran state back at the TI ResponseEvent responseEvent = eventCollector.extractCollectedResponseEvent(); assertNotNull( "The Tested Implementation did not pass a 1xx response to the TU!", responseEvent); assertNotNull( "The Tested Implementation did not pass a 1xx response to the TU!", responseEvent.getResponse()); assertTrue( "A response different from TYING was passed to the TU!", responseEvent.getResponse().getStatusCode()==Response.TRYING); assertSame( "The TRYING response was not associated with the right transaction.", tran, responseEvent.getClientTransaction()); //verify the the tran state is now PROCEEDING assertEquals( "The ClientTransaction did not pass in the PROCEEDING state after " +"receiving 1xx provisional response", tran.getState(), TransactionState.PROCEEDING ); // Send a CANCEL from the TI Request tiCancel = tran.createCancel(); /* this works, but since we cannot patch the INVITE the test fails if (!rfc3261Compliant) { ((ViaHeader) tiCancel.getHeader("Via")).setBranch( "xxx" ); // Not allowed by RI // ((FromHeader) tiCancel.getHeader("From")).setTag( "" ); }*/ ClientTransaction tiCancelTrans; try { eventCollector.collectRequestEvent(riSipProvider); tiCancelTrans = tiSipProvider.getNewClientTransaction(tiCancel); tiCancelTrans.sendRequest(); } catch(SipException ex) { throw new TiUnexpectedError( "A SipExceptionOccurred while trying to send CANCEL!", ex); } waitForMessage(); RequestEvent cancelReceivedEvent = eventCollector.extractCollectedRequestEvent(); if(cancelReceivedEvent==null||cancelReceivedEvent.getRequest()==null) throw new TiUnexpectedError( "The CANCEL request was not received by the RI!"); // Send 200 OK to the CANCEL try { eventCollector.collectResponseEvent(tiSipProvider); } catch( TooManyListenersException ex ) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } Response riCancelOk = null; try { riCancelOk = riMessageFactory.createResponse( Response.OK, cancelReceivedEvent.getRequest() ); addStatus(cancelReceivedEvent.getRequest(),riCancelOk); riSipProvider.sendResponse( riCancelOk ); } catch( Throwable ex ) { throw new TckInternalError( "The TCK could not send a CANCEL OK response back to the TI", ex); } waitForMessage(); // Analyze the OK response and Tran state back at the TI responseEvent = eventCollector.extractCollectedResponseEvent(); if (responseEvent==null||responseEvent.getResponse()==null) { throw new TiUnexpectedError( "The CANCEL OK response was not received by the TI!"); } // Send 487 to the INVITE, expect ACK try { eventCollector.collectResponseEvent(tiSipProvider); } catch( TooManyListenersException ex ) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } SipEventCollector ackCollector = new SipEventCollector(); try { ackCollector.collectRequestEvent(riSipProvider); } catch( TooManyListenersException ex ) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } Response riInviteTerminated = null; try { riInviteTerminated = riMessageFactory.createResponse( Response.REQUEST_TERMINATED, inviteReceivedEvent.getRequest() ); addStatus(inviteReceivedEvent.getRequest(),riInviteTerminated); riSipProvider.sendResponse( riInviteTerminated ); } catch( Throwable ex ) { throw new TckInternalError( "The TCK could not send a INVITE 487 response back to the TI", ex); } waitForMessage(); //Analyze the REQUEST_TERMINATED response and Tran state back at the TI responseEvent = eventCollector.extractCollectedResponseEvent(); assertNotNull( "The Tested Implementation did not pass a 300-699 response to the TU!", responseEvent); assertNotNull( "The Tested Implementation did not pass a 300-699 response to the TU!", responseEvent.getResponse()); assertSame( "The 487 response was not associated with the right transaction", tran, responseEvent.getClientTransaction() ); assertEquals( "A response different from 487 was passed to the TU", Response.REQUEST_TERMINATED, responseEvent.getResponse().getStatusCode() ); assertEquals( "The ClientTransaction did not pass in the COMPLETED state after " +"receiving 300-699 final response", tran.getState(), TransactionState.COMPLETED ); //check whether the ackCollector has caught any fish RequestEvent ackReceivedEvent = ackCollector. extractCollectedRequestEvent(); assertNotNull("The TI did not send an ACK request event", ackReceivedEvent); assertNotNull("The TI did not send an ACK request", ackReceivedEvent.getRequest()); assertEquals(Request.ACK, ackReceivedEvent.getRequest().getMethod()); //Try to kill remaining ACK retransmissions? } catch( Throwable exc ) { exc.printStackTrace(); fail(exc.getClass().getName()+": "+exc.getMessage()); } assertTrue(new Exception().getStackTrace()[0].toString(), true); //Unfortunately we can't assert the TERMINATED state as TIMER_K TIMER_D //is not exported by JAIN SIP } /** * Tries to steer a TI client transaction through the following scenario * Calling-->Completed-->Terminated. Apart from state * transitions, we also test, retransmissions and proper hiding/passing * of messages to the TU. */ public void testCallingCompletedTerminatedScenario() { try { Request invite = createTiInviteRequest(null, null, null); // JvB: test // ((MessageExt) invite).getFirstViaHeader().setParameter( "rport", null ); ClientTransaction tran = null; try { eventCollector.collectRequestEvent(riSipProvider); tran = tiSipProvider.getNewClientTransaction(invite); tran.sendRequest(); } catch(SipException ex) { throw new TiUnexpectedError( "A SipExceptionOccurred while trying to send request!", ex); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } waitForMessage(); RequestEvent inviteReceivedEvent = eventCollector. extractCollectedRequestEvent(); if(inviteReceivedEvent==null ||inviteReceivedEvent.getRequest()==null) throw new TiUnexpectedError( "The invite request was not received by the RI!"); //At this point the ClientTransaction should be CALLING! assertEquals(TransactionState.CALLING, tran.getState()); //Check Request retransmission try { eventCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } //Wait for the retransmission timer to fire if it had not already done so. if(tran.getRetransmitTimer()>MESSAGES_ARRIVE_FOR) sleep( (long)tran.getRetransmitTimer() -MESSAGES_ARRIVE_FOR); //subtract the time we waited for the invite //Wait for the retransmitted request to arrive waitForMessage(); inviteReceivedEvent = eventCollector.extractCollectedRequestEvent(); assertNotNull("The invite request was not retransmitted!", inviteReceivedEvent); assertNotNull("The invite request was not retransmitted!", inviteReceivedEvent.getRequest()); assertEquals(Request.INVITE, inviteReceivedEvent.getRequest().getMethod()); //At this point the ClientTransaction should STILL be CALLING! assertEquals(TransactionState.CALLING, tran.getState()); //Send a 486 BUSY HERE (final) response from the RI try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } //The BUSY_HERE response should trigger some ACKs so let's register //a listener with the RI SipEventCollector ackCollector = new SipEventCollector(); try { ackCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } Response busyHere = null; try { busyHere = riMessageFactory.createResponse ( Response.BUSY_HERE, inviteReceivedEvent.getRequest()); addStatus( inviteReceivedEvent.getRequest(), busyHere); // JvB: set to-tag, check it against the ACK generated ((ToHeader) busyHere.getHeader("to")).setTag( "ack-to-test" ); riSipProvider.sendResponse((Response)busyHere.clone()); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send a BUSY HERE response back to the TI", ex); } waitForMessage(); //Analyze the BUSY_HERE response and Tran state back at the TI ResponseEvent responseEvent = eventCollector.extractCollectedResponseEvent(); assertNotNull( "The Tested Implementation did not pass a 300-699 response to the TU!", responseEvent); assertNotNull( "The Tested Implementation did not pass a 300-699 response to the TU!", responseEvent.getResponse()); assertSame( "The BUSY_HERE response was not associated with the right transaction", tran, responseEvent.getClientTransaction()); assertSame( "A response different from BUSY_HERE was passed to the TU", tran, responseEvent.getClientTransaction()); assertEquals( "The ClientTransaction did not pass in the COMPLETED state after " +"receiving 300-699 final response", tran.getState(), TransactionState.COMPLETED ); //check whether the ackCollector has caught any fish RequestEvent ackReceivedEvent = ackCollector. extractCollectedRequestEvent(); assertNotNull("The TI did not send an ACK request", ackReceivedEvent); assertNotNull("The TI did not send an ACK request", ackReceivedEvent.getRequest()); assertEquals(Request.ACK, ackReceivedEvent.getRequest().getMethod()); //Try to kill remaining ACK retransmissions //TODO this may not always work .. should give it a specific timeout value waitForMessage(); //Now let's retransmit the final response. This time it shouldn't be //passed to the TU but an ACK should still be sent try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } //go fish the ack try { ackCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } try { riSipProvider.sendResponse((Response)busyHere.clone()); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send a BUSY HERE response back to the TI", ex); } waitForMessage(); //The TU shouldn't see the retransmitted BUSY_HERE response responseEvent = eventCollector.extractCollectedResponseEvent(); assertNull( "The Tested Implementation passed a retransmitted 300-699 response " +"to the TU instead of just silently acknowledging it!", responseEvent); //We must still be in the completed state. assertEquals( "The ClientTransaction did not stay long enough in the COMPLETED " +"state.", tran.getState(), TransactionState.COMPLETED ); //check whether the ackCollector has caught any fish ackReceivedEvent = ackCollector.extractCollectedRequestEvent(); assertNotNull( "The TI did not send an ACK request to the second response", ackReceivedEvent); assertNotNull( "The TI did not send an ACK request to the second response", ackReceivedEvent.getRequest()); assertEquals(Request.ACK, ackReceivedEvent.getRequest().getMethod()); // JvB: verify to tag assertEquals( "The To header field in the ACK MUST equal the To header field " + " in the response being acknowledged", "ack-to-test", ((ToHeader)ackReceivedEvent.getRequest().getHeader("to")).getTag() ); } catch(Throwable exc) { exc.printStackTrace(); fail(exc.getClass().getName()+": "+exc.getMessage()); } assertTrue(new Exception().getStackTrace()[0].toString(), true); //Unfortunately we can't assert the TERMINATED state as TIMER_D //is not exported by JAIN SIP } /** * Tries to steer a TI client transaction through the following scenario * Calling-->Proceeding-->Terminated. Apart from state * transitions, we also test, retransmissions and proper hiding/passing * of messages to the TU. */ public void testCallingProceedingTerminatedScenario() { try { Request invite = createTiInviteRequest(null, null, null); ClientTransaction tran = null; try { eventCollector.collectRequestEvent(riSipProvider); tran = tiSipProvider.getNewClientTransaction(invite); tran.sendRequest(); } catch(SipException ex) { throw new TiUnexpectedError( "A SipExceptionOccurred while trying to send request!", ex); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } waitForMessage(); RequestEvent inviteReceivedEvent = eventCollector. extractCollectedRequestEvent(); if(inviteReceivedEvent==null ||inviteReceivedEvent.getRequest()==null) throw new TiUnexpectedError( "The invite request was not received by the RI!"); //At this point the ClientTransaction should be CALLING! assertEquals(TransactionState.CALLING, tran.getState()); //Check Request retransmission try { eventCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } //Wait for the retransmission timer to fire if it had not already done so. if(tran.getRetransmitTimer()>MESSAGES_ARRIVE_FOR) sleep( (long)tran.getRetransmitTimer() -MESSAGES_ARRIVE_FOR); //subtract the time we waited for the invite //Wait for the retransmitted request to arrive waitForMessage(); inviteReceivedEvent = eventCollector.extractCollectedRequestEvent(); assertNotNull("The invite request was not retransmitted!", inviteReceivedEvent); assertNotNull("The invite request was not retransmitted!", inviteReceivedEvent.getRequest()); assertEquals(Request.INVITE, inviteReceivedEvent.getRequest().getMethod()); //At this point the ClientTransaction should STILL be CALLING! assertEquals(TransactionState.CALLING, tran.getState()); //Send a TRYING response try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } try { Response resp = riMessageFactory.createResponse( Response.TRYING, inviteReceivedEvent.getRequest()); addStatus( inviteReceivedEvent.getRequest(), resp); riSipProvider.sendResponse(resp); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send a trying response back to the TI", ex); } waitForMessage(); //Analyze the TRYING response and Tran state back at the TI ResponseEvent responseEvent = eventCollector.extractCollectedResponseEvent(); assertNotNull( "The Tested Implementation did not pass a 1xx response to the TU!", responseEvent); assertNotNull( "The Tested Implementation did not pass a 1xx response to the TU!", responseEvent.getResponse()); assertTrue( "A response different from TYING was passed to the TU!", responseEvent.getResponse().getStatusCode()==Response.TRYING); assertSame( "The TRYING response was not associated with the right transaction", tran, responseEvent.getClientTransaction()); //verify the the tran state is now PROCEEDING assertEquals( "The ClientTransaction did not pass in the PROCEEDING state after " +"receiving 1xx provisional response", tran.getState(), TransactionState.PROCEEDING ); //Send a 200 OK (final) response from the RI try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } //The OK response shouldn't trigger any ACKs so let's register //a listener with the RI to verify whether that is the case SipEventCollector ackCollector = new SipEventCollector(); try { ackCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } Response ok = null; try { ok = riMessageFactory.createResponse(Response.OK, inviteReceivedEvent.getRequest()); // JvB: MUST add Contact too! ContactHeader contact = riHeaderFactory.createContactHeader( ((ToHeader) ok.getHeader("To")).getAddress() ); ok.addHeader( contact ); // end JvB addStatus(inviteReceivedEvent.getRequest(),ok); riSipProvider.sendResponse((Response)ok.clone()); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send an OK response back to the TI", ex); } waitForMessage(); //Analyze the OK response and Tran state back at the TI responseEvent = eventCollector.extractCollectedResponseEvent(); assertNotNull( "The Tested Implementation did not pass a 200 OK response to the TU!", responseEvent); assertNotNull( "The Tested Implementation did not pass a 200 OK response to the TU!", responseEvent.getResponse()); assertSame( "The OK response was not associated with the right transaction", tran, responseEvent.getClientTransaction()); assertSame( "A response different from OK was passed to the TU", tran, responseEvent.getClientTransaction()); assertEquals( "The ClientTransaction did not pass in the TERMINATED state after " +"receiving 200 final response", tran.getState(), TransactionState.TERMINATED ); //check whether the ackCollector has caught any fish RequestEvent ackReceivedEvent = ackCollector. extractCollectedRequestEvent(); if(ackReceivedEvent!= null) logger.error("Shouldn't have received that="+ackReceivedEvent.getRequest()); assertNull("The TI sent an ACK to an OK (this is TU's job)!", ackReceivedEvent); //Now let's retransmit the final response and see it is //passed to the TU (again no ACKs) try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } //go fish the ack try { ackCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } try { riSipProvider.sendResponse((Response)ok.clone()); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send an OK response back to the TI", ex); } waitForMessage(); //Did we get the 2nd OK? responseEvent = eventCollector.extractCollectedResponseEvent(); /* * JvB: the stack should in fact ~not~ pass the retransmitted response, but filter * it out * assertNotNull( "The TI did not pass to the TU a retransmitted OK response!", responseEvent); assertNotNull( "The TI did not pass to the TU a retransmitted OK response!", responseEvent.getResponse()); assertTrue( "The TI passed to the TU a bad response!", responseEvent.getResponse().getStatusCode()==Response.OK); */ // TBD // assertNull( "The TI should filter the retransmitted OK response", responseEvent ); //We must still be in the terminated state. assertEquals( "The ClientTransaction mysteriously left the TERMINATED state!", tran.getState(), TransactionState.TERMINATED ); //check whether the ackCollector has caught any fish ackReceivedEvent = ackCollector.extractCollectedRequestEvent(); assertNull( "The TI sent an ACK request to the second OK response " +"(OK acks are TU's responsibility)!", ackReceivedEvent); } catch(Throwable exc) { exc.printStackTrace(); fail(exc.getClass().getName()+": "+exc.getMessage()); } assertTrue(new Exception().getStackTrace()[0].toString(), true); } /** * Tries to steer a TI client transaction through the following scenario * Calling-->Terminated. Apart from state * transitions, we also test, retransmissions and proper hiding/passing * of messages to the TU. */ public void testCallingTerminatedScenario() { try { Request invite = createTiInviteRequest(null, null, null); ClientTransaction tran = null; try { eventCollector.collectRequestEvent(riSipProvider); tran = tiSipProvider.getNewClientTransaction(invite); tran.sendRequest(); } catch(SipException ex) { throw new TiUnexpectedError( "A SipExceptionOccurred while trying to send request!", ex); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } waitForMessage(); RequestEvent inviteReceivedEvent = eventCollector. extractCollectedRequestEvent(); if(inviteReceivedEvent==null ||inviteReceivedEvent.getRequest()==null) throw new TiUnexpectedError( "The invite request was not received by the RI!"); //At this point the ClientTransaction should be CALLING! assertEquals(TransactionState.CALLING, tran.getState()); //Check Request retransmission try { eventCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } //Wait for the retransmission timer to fire if it had not already done so. if(tran.getRetransmitTimer()>MESSAGES_ARRIVE_FOR) sleep( (long)tran.getRetransmitTimer() -MESSAGES_ARRIVE_FOR); //subtract the time we waited for the invite //Wait for the retransmitted request to arrive waitForMessage(); inviteReceivedEvent = eventCollector.extractCollectedRequestEvent(); assertNotNull("The invite request was not retransmitted!", inviteReceivedEvent); assertNotNull("The invite request was not retransmitted!", inviteReceivedEvent.getRequest()); assertEquals(Request.INVITE, inviteReceivedEvent.getRequest().getMethod()); //At this point the ClientTransaction should STILL be CALLING! assertEquals(TransactionState.CALLING, tran.getState()); //Send a 200 OK (final) response from the RI try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } //The OK response shouldn't trigger any ACKs so let's register //a listener with the RI to verify whether that is the case SipEventCollector ackCollector = new SipEventCollector(); try { ackCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } Response ok = null; try { ok = riMessageFactory.createResponse( Response.OK, inviteReceivedEvent.getRequest()); addStatus(inviteReceivedEvent.getRequest(),ok); // JvB: MUST add Contact too! ContactHeader contact = riHeaderFactory.createContactHeader( ((ToHeader) ok.getHeader("To")).getAddress() ); ok.addHeader( contact ); // end JvB riSipProvider.sendResponse(ok); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send an OK response back to the TI", ex); } waitForMessage(); //Analyze the OK response and Tran state back at the TI ResponseEvent responseEvent = eventCollector.extractCollectedResponseEvent(); assertNotNull( "The Tested Implementation did not pass a 200 OK response to the TU!", responseEvent); assertNotNull( "The Tested Implementation did not pass a 200 OK response to the TU!", responseEvent.getResponse()); assertSame( "The OK response was not associated with the right transaction", tran, responseEvent.getClientTransaction()); assertSame( "A response different from OK was passed to the TU", tran, responseEvent.getClientTransaction()); assertEquals( "The ClientTransaction did not pass in the TERMINATED state after " +"receiving 200 final response", tran.getState(), TransactionState.TERMINATED ); //check whether the ackCollector has caught any fish RequestEvent ackReceivedEvent = ackCollector. extractCollectedRequestEvent(); assertNull("The TI sent an ACK to an OK (this is TU's job)!", ackReceivedEvent); //Now let's retransmit the final response and see that it is //passed to the TU (again no ACKs should be sent by the TI) try { eventCollector.collectResponseEvent(tiSipProvider); } catch(TooManyListenersException ex) { throw new TiUnexpectedError( "Failed to register a SipListener with TI", ex); } //go fish the ack try { ackCollector.collectRequestEvent(riSipProvider); } catch(TooManyListenersException ex) { throw new TckInternalError( "Failed to regiest a SipListener with an RI SipProvider", ex); } try { riSipProvider.sendResponse((Response)ok.clone()); } catch(Throwable ex) { throw new TckInternalError( "The TCK could not send an OK response back to the TI", ex); } waitForMessage(); //Did we get the 2nd OK? responseEvent = eventCollector.extractCollectedResponseEvent(); /* * JvB: see previous comment * assertNotNull( "The TI did not pass to the TU a retransmitted OK response!", responseEvent); assertNotNull( "The TI did not pass to the TU a retransmitted OK response!", responseEvent.getResponse()); assertTrue( "The TI passed to the TU a bad response!", responseEvent.getResponse().getStatusCode()==Response.OK); */ // JvB: TBD // assertNull( "The TI should filter the retransmitted OK response", responseEvent ); //We must still be in the terminated state. assertEquals( "The ClientTransaction mysteriously left the TERMINATED state!", tran.getState(), TransactionState.TERMINATED ); //check whether the ackCollector has caught any fish ackReceivedEvent = ackCollector.extractCollectedRequestEvent(); assertNull( "The TI sent an ACK request to the second OK response " +"(OK acks are TU's responsibility)!", ackReceivedEvent); } catch(Throwable exc) { exc.printStackTrace(); fail(exc.getClass().getName()+": "+exc.getMessage()); } assertTrue(new Exception().getStackTrace()[0].toString(), true); } //==================== end of tests //====== STATIC JUNIT ========== public static Test suite() { return new TestSuite( InviteClientTransactionsStateMachineTest.class ); } public static void main( String[] args ) { junit.swingui.TestRunner.run( InviteClientTransactionsStateMachineTest.class ); } }