001: /*
002: * Conditions Of Use
003: *
004: * This software was developed by employees of the National Institute of
005: * Standards and Technology (NIST), and others.
006: * This software is has been contributed to the public domain.
007: * As a result, a formal license is not needed to use the software.
008: *
009: * This software is provided "AS IS."
010: * NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
011: * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
012: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
013: * AND DATA ACCURACY. NIST does not warrant or make any representations
014: * regarding the use of the software or the results thereof, including but
015: * not limited to the correctness, accuracy, reliability or usefulness of
016: * the software.
017: *
018: *
019: */
020: package test.tck.msgflow;
021:
022: import junit.framework.*;
023:
024: import javax.sip.*;
025: import javax.sip.message.*;
026: import javax.sip.header.*;
027: import java.util.*;
028: import test.tck.*;
029:
030: /**
031: *
032: * Tests whether the dialog state machine is properly implemented. Dialog state
033: * machines are far less complex than transaction FSMs. All they do is passively
034: * change states without producing any automatic responses or retransmissions.
035: * Therefore we're only testing one scenario per Dialog (one for Server generated
036: * and one for Client generated dialogs. Distinction is made server and client
037: * generated scenarios are likely to be navigated by server and client transactions
038: * respectively and not by a single entity)
039: *
040: * <p>Title: TCK</p>
041: * <p>Description: JAIN SIP 1.1 Technology Compatibility Kit</p>
042: * @author Emil Ivov
043: * Network Research Team, Louis Pasteur University, Strasbourg, France.
044: * This code is in the public domain.
045: * @version 1.0
046: */
047:
048: public class DialogStateMachineTest extends MessageFlowHarness {
049:
050: public DialogStateMachineTest(String name) {
051: super (name, true); // enable auto-dialog support
052: }
053:
054: //==================== tests ==============================
055: /**
056: * Tests state transitions of a server side dialog
057: */
058: public void testClientDialogStates() {
059: try {
060: Request invite = createTiInviteRequest(null, null, null);
061: ClientTransaction tran = null;
062: //Send an invite request
063: try {
064: eventCollector.collectRequestEvent(riSipProvider);
065: tran = tiSipProvider.getNewClientTransaction(invite);
066: tran.sendRequest();
067: } catch (TooManyListenersException e) {
068: throw new TckInternalError(
069: "Failed to register a listener with the RI", e);
070: } catch (SipException e) {
071: throw new TiUnexpectedError(
072: "Failed to send initial invite request", e);
073: }
074: //Wait for the invite to arrive
075: waitForMessage();
076: RequestEvent inviteReqEvt = eventCollector
077: .extractCollectedRequestEvent();
078: if (inviteReqEvt == null
079: || inviteReqEvt.getRequest() == null)
080: throw new TiUnexpectedError(
081: "The TI did not send the initial invite request");
082: //get the dialog
083: Dialog dialog = tran.getDialog();
084: //We should have a null state here
085: assertNull("A dialog passed into the " + dialog.getState()
086: + " state before receiving any response!", dialog
087: .getState());
088:
089: //We will now send RINGING response and see that the Dialog enters an early state
090: //start listening for the response
091: try {
092: eventCollector.collectResponseEvent(tiSipProvider);
093: } catch (TooManyListenersException e) {
094: throw new TiUnexpectedError(
095: "Failed to register a SipListener with the TI.",
096: e);
097: }
098: Response ringing = null;
099: try {
100: ringing = riMessageFactory.createResponse(
101: Response.RINGING, inviteReqEvt.getRequest());
102: ((ToHeader) ringing.getHeader(ToHeader.NAME))
103: .setTag(Integer.toString(hashCode()));
104: // BUG: set contact header on dialog-creating response
105: ringing.setHeader(createRiContact());
106: riSipProvider.sendResponse(ringing);
107: } catch (Exception e) {
108: throw new TckInternalError(
109: "Failed to create and send a RINGING response",
110: e);
111: }
112: waitForMessage();
113: ResponseEvent ringingRespEvt = eventCollector
114: .extractCollectedResponseEvent();
115: if (ringingRespEvt == null
116: || ringingRespEvt.getResponse() == null)
117: throw new TiUnexpectedError(
118: "The TI did not pass RINGING response to the TU.");
119: //The dialog should now be in its early state.
120: assertEquals(
121: "The Dialog did not pass into the early state upon reception of a RINGING response",
122: DialogState.EARLY, dialog.getState());
123:
124: // JvB: @todo Test that UPDATE (or PRACK) requests during early dialog
125: // dont make dialog CONFIRMED
126: /*
127: Request update = null;
128: try {
129: eventCollector.collectRequestEvent(riSipProvider);
130: update = dialog.createRequest( "UPDATE" );
131: update.setHeader(createTiContact());
132: dialog.sendRequest( tiSipProvider.getNewClientTransaction(update) );
133: } catch (Exception e) {
134: throw new TckInternalError(
135: "Failed to create and send an UPDATE request",
136: e);
137: }
138: waitForMessage();
139: RequestEvent updateEvt =
140: eventCollector.extractCollectedRequestEvent();
141: if (updateEvt == null || updateEvt.getRequest() == null)
142: throw new TiUnexpectedError("The TI did not send the UPDATE request");
143: //The dialog should still be in its EARLY state.
144: assertEquals(
145: "The Dialog did not stay in the EARLY state upon reception of an UPDATE request",
146: DialogState.EARLY,
147: dialog.getState());
148:
149: // finish the UPDATE
150: riSipProvider.sendResponse( riMessageFactory.createResponse(200, updateEvt.getRequest()) );
151: */
152:
153: //We will now send OK response and see that the Dialog enters a CONFIRMED state
154: //start listening for the response
155: try {
156: eventCollector.collectResponseEvent(tiSipProvider);
157: } catch (TooManyListenersException e) {
158: throw new TiUnexpectedError(
159: "Failed to register a SipListener with the TI.",
160: e);
161: }
162: Response ok = null;
163: try {
164: ok = riMessageFactory.createResponse(Response.OK,
165: inviteReqEvt.getRequest());
166: ((ToHeader) ok.getHeader(ToHeader.NAME)).setTag(Integer
167: .toString(hashCode()));
168: // BUG: set contact header on dialog-creating response
169: ok.setHeader(createRiContact());
170: riSipProvider.sendResponse(ok);
171: } catch (Exception e) {
172: throw new TckInternalError(
173: "Failed to create and send a OK response", e);
174: }
175: waitForMessage();
176: ResponseEvent okRespEvt = eventCollector
177: .extractCollectedResponseEvent();
178: if (okRespEvt == null || okRespEvt.getResponse() == null)
179: throw new TiUnexpectedError(
180: "The TI did not pass OK response to the TU.");
181: //The dialog should now be in its confirmed state.
182: assertEquals(
183: "The Dialog did not pass into the CONFIRMED state upon reception of an OK response",
184: DialogState.CONFIRMED, dialog.getState());
185: //Say bye and go COMPLETED
186: Request bye = null;
187: try {
188: bye = dialog.createRequest(Request.BYE);
189: ClientTransaction byeTran = tiSipProvider
190: .getNewClientTransaction(bye);
191: dialog.sendRequest(byeTran);
192: } catch (SipException e) {
193: throw new TiUnexpectedError(
194: "Failed to create and send a BYE request using a dialog.",
195: e);
196: }
197: // Send response before checking that the state goes to
198: // terminated state.
199: waitForMessage();
200: tiSipProvider.sendResponse(tiMessageFactory.createResponse(
201: 200, bye));
202: waitForMessage();
203: assertTrue(
204: "The dialog did not pass into a final ( TERMINATED) state after getting OK for a BYE.",
205: DialogState.TERMINATED.equals(dialog.getState()));
206: } catch (Throwable exc) {
207: exc.printStackTrace();
208: fail(exc.getClass().getName() + ": " + exc.getMessage());
209: }
210:
211: assertTrue(new Exception().getStackTrace()[0].toString(), true);
212:
213: }
214:
215: /**
216: * Tests state transitions of a client side dialog
217: */
218: public void testServerDialogStates() {
219: try {
220: ClientTransaction inviteTransaction = null;
221: Request invite = createRiInviteRequest(null, null, null);
222: //Send an invite request
223: try {
224: eventCollector.collectRequestEvent(tiSipProvider);
225: // riSipProvider.sendRequest(invite);
226: // Made this stateful
227: inviteTransaction = riSipProvider
228: .getNewClientTransaction(invite);
229:
230: inviteTransaction.sendRequest();
231: } catch (TooManyListenersException exc) {
232: throw new TiUnexpectedError(
233: "Failed to register a listener with the TI",
234: exc);
235: } catch (SipException exc) {
236: throw new TckInternalError(
237: "Failed to send initial invite request", exc);
238: }
239: //Wait for the invite to arrive
240: waitForMessage();
241: RequestEvent inviteReqEvt = eventCollector
242: .extractCollectedRequestEvent();
243: if (inviteReqEvt == null
244: || inviteReqEvt.getRequest() == null)
245: throw new TiUnexpectedError(
246: "The TI did not dispatch the initial invite request");
247: //Create a transaction
248: ServerTransaction tran = null;
249: try {
250: tran = tiSipProvider
251: .getNewServerTransaction(inviteReqEvt
252: .getRequest());
253: } catch (Exception ex) {
254: throw new TiUnexpectedError(
255: "The TI failed to create a Server transaction for an incoming request");
256: }
257: //get the dialog
258: Dialog dialog = tran.getDialog();
259:
260: // We should have a null state here
261: assertNull("A dialog passed into the " + dialog.getState()
262: + " state before sending any response!", dialog
263: .getState());
264:
265: //We will now send RINGING response and see that the Dialog enters an early state
266: //start listening for the response
267: try {
268: eventCollector.collectResponseEvent(riSipProvider);
269: } catch (TooManyListenersException e) {
270: throw new TckInternalError(
271: "Failed to register a SipListener with the RI.",
272: e);
273: }
274: Response ringing = null;
275: try {
276: ringing = tiMessageFactory.createResponse(
277: Response.RINGING, inviteReqEvt.getRequest());
278: //!discuss with Ranga
279: ((ToHeader) ringing.getHeader(ToHeader.NAME))
280: .setTag(Integer.toString(hashCode()));
281: // BUG report by Ben Evans (Open cloud):
282: // set contact header on dialog-creating response
283: ringing.setHeader(createTiContact());
284: tran.sendResponse(ringing);
285: } catch (Exception e) {
286: throw new TiUnexpectedError(
287: "Failed to create and send a RINGING response",
288: e);
289: }
290: waitForMessage();
291: ResponseEvent ringingRespEvt = eventCollector
292: .extractCollectedResponseEvent();
293: if (ringingRespEvt == null
294: || ringingRespEvt.getResponse() == null)
295: throw new TiUnexpectedError(
296: "The TI did not send the RINGING response.");
297: //The dialog should now be in its early state.
298: assertEquals(
299: "The Dialog did not pass into the early state after sending a RINGING response",
300: DialogState.EARLY, dialog.getState());
301: //We will now send an OK response and see that the Dialog enters a CONFIRMED state
302: //start listening for the response
303: try {
304: eventCollector.collectResponseEvent(riSipProvider);
305: } catch (TooManyListenersException e) {
306: throw new TckInternalError(
307: "Failed to register a SipListener with the RI.",
308: e);
309: }
310: Response ok = null;
311: try {
312: ok = tiMessageFactory.createResponse(Response.OK,
313: inviteReqEvt.getRequest());
314: // Bug - need to set to-tag on OK response
315: // NIST-SIP fills in the to-tag but this behaviour is
316: // not specified
317: ((ToHeader) ok.getHeader(ToHeader.NAME)).setTag(Integer
318: .toString(hashCode()));
319: ContactHeader contact = createTiContact();
320: ok.addHeader(contact);
321:
322: tran.sendResponse(ok);
323: } catch (Exception e) {
324: throw new TiUnexpectedError(
325: "Failed to create and send an OK response", e);
326: }
327: waitForMessage();
328: ResponseEvent okRespEvt = eventCollector
329: .extractCollectedResponseEvent();
330: if (okRespEvt == null || okRespEvt.getResponse() == null)
331: throw new TiUnexpectedError(
332: "The TI did not send an OK response.");
333: ClientTransaction ct = okRespEvt.getClientTransaction();
334:
335: // JvB: With auto-dialog-support OFF, this returns *null* !
336: Dialog clientDialog = ct.getDialog();
337: assertNotNull(clientDialog);
338: Request ackReq = clientDialog
339: .createAck(((CSeqHeader) okRespEvt.getResponse()
340: .getHeader(CSeqHeader.NAME)).getSeqNumber());
341: ;
342: clientDialog.sendAck(ackReq);
343: waitForMessage();
344: //The dialog should now be in its CONFIRMED state.
345: assertEquals(
346: "The Dialog did not pass into the CONFIRMED state upon reception of an OK response",
347: DialogState.CONFIRMED, dialog.getState());
348: //Say bye from the RI and see that TI goes COMPLETED
349: //it is the ri that should say bye here as we are testing dialog navigation
350: //by server transactions
351: try {
352: eventCollector.collectRequestEvent(tiSipProvider);
353: } catch (TooManyListenersException ex) {
354: throw new TiUnexpectedError(
355: "Failed to register a SipListener with the TI",
356: ex);
357: }
358: try {
359: // Ranga - use the dialog here.
360: Dialog d = inviteTransaction.getDialog();
361: Request bye = d.createRequest(Request.BYE);
362: ct = riSipProvider.getNewClientTransaction(bye);
363: d.sendRequest(ct);
364:
365: } catch (Exception e) {
366: throw new TckInternalError(
367: "Failed to create and send a BYE request using a dialog.",
368: e);
369: }
370: waitForMessage();
371: RequestEvent byeEvt = eventCollector
372: .extractCollectedRequestEvent();
373: if (byeEvt == null || byeEvt.getRequest() == null)
374: throw new TiUnexpectedError(
375: "The TI did not dispatch a BYE request");
376: ServerTransaction byeTran = null;
377:
378: /** This should be in the transaction
379:
380: try
381: {
382: byeTran = tiSipProvider.getNewServerTransaction(byeEvt.getRequest());
383: } catch (TransactionUnavailableException ex) {
384: // Could have already fielded the bye - in which case a
385: // transaction for the BYE cannot be created.
386: ex.printStackTrace();
387: assertTrue(new Exception().getStackTrace()[0].toString(), true);
388: } catch(Exception ex) {
389: ex.printStackTrace();
390: System.out.println("Failed to create a transaction for an incoming bye request.");
391: }
392: **/
393: byeTran = (ServerTransaction) byeEvt.getServerTransaction();
394: //We will now send an OK response and see that the
395: // Dialog enters a COMPLETED/TERMINATED state
396: //start listening for the response
397: try {
398: eventCollector.collectResponseEvent(riSipProvider);
399: } catch (TooManyListenersException e) {
400: throw new TckInternalError(
401: "Failed to register a SipListener with the RI.",
402: e);
403: }
404: try {
405: ok = tiMessageFactory.createResponse(Response.OK,
406: byeEvt.getRequest());
407: ok.addHeader(createTiContact());
408: byeTran.sendResponse(ok);
409: } catch (Exception e) {
410: throw new TiUnexpectedError(
411: "Failed to create and send an OK response", e);
412: }
413: waitForMessage();
414: ResponseEvent byeOkRespEvt = eventCollector
415: .extractCollectedResponseEvent();
416: if (byeOkRespEvt == null
417: || byeOkRespEvt.getResponse() == null)
418: throw new TiUnexpectedError(
419: "The TI did not send an OK response to a bye request.");
420: //The dialog should now be in the COMPLETED/TERMINATED state.
421: assertTrue(
422: "The dialog did not pass into a final (COMPLETED or TERMINATED) state after recieving a BYE.",
423: DialogState.COMPLETED.equals(dialog.getState())
424: || DialogState.TERMINATED.equals(dialog
425: .getState()));
426: } catch (Throwable exc) {
427: exc.printStackTrace();
428: fail(exc.getClass().getName() + ": " + exc.getMessage());
429: }
430:
431: assertTrue(new Exception().getStackTrace()[0].toString(), true);
432:
433: }
434:
435: //==================== end of tests
436:
437: //====== STATIC JUNIT ==========
438: public static Test suite() {
439: return new TestSuite(DialogStateMachineTest.class);
440: }
441:
442: }
|