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
 * <pre>
 *
 *                               |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
 *
 * </pre>
 *
 * 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 );
    }

}