001: package test.tck.msgflow.callflows.refer;
002:
003: import java.text.ParseException;
004: import java.util.ArrayList;
005: import java.util.Properties;
006:
007: import javax.sip.ClientTransaction;
008: import javax.sip.Dialog;
009: import javax.sip.DialogState;
010: import javax.sip.DialogTerminatedEvent;
011: import javax.sip.IOExceptionEvent;
012: import javax.sip.InvalidArgumentException;
013: import javax.sip.ListeningPoint;
014: import javax.sip.PeerUnavailableException;
015: import javax.sip.RequestEvent;
016: import javax.sip.ResponseEvent;
017: import javax.sip.ServerTransaction;
018: import javax.sip.SipException;
019: import javax.sip.SipFactory;
020: import javax.sip.SipListener;
021: import javax.sip.SipProvider;
022: import javax.sip.SipStack;
023: import javax.sip.Transaction;
024: import javax.sip.TransactionTerminatedEvent;
025: import javax.sip.address.Address;
026: import javax.sip.address.AddressFactory;
027: import javax.sip.address.SipURI;
028: import javax.sip.header.CSeqHeader;
029: import javax.sip.header.CallIdHeader;
030: import javax.sip.header.ContactHeader;
031: import javax.sip.header.ContentTypeHeader;
032: import javax.sip.header.EventHeader;
033: import javax.sip.header.ExpiresHeader;
034: import javax.sip.header.FromHeader;
035: import javax.sip.header.HeaderFactory;
036: import javax.sip.header.MaxForwardsHeader;
037: import javax.sip.header.ReferToHeader;
038: import javax.sip.header.SubscriptionStateHeader;
039: import javax.sip.header.ToHeader;
040: import javax.sip.header.ViaHeader;
041: import javax.sip.message.MessageFactory;
042: import javax.sip.message.Request;
043: import javax.sip.message.Response;
044:
045: import org.apache.log4j.FileAppender;
046: import org.apache.log4j.Level;
047: import org.apache.log4j.Logger;
048: import org.apache.log4j.SimpleLayout;
049:
050: import test.tck.TestHarness;
051: import test.tck.msgflow.callflows.ProtocolObjects;
052:
053: /**
054: * This example shows an out-of-dialog REFER scenario:
055: *
056: * referer sends REFER to referee, with Refer-To set to Shootme
057: * referee sends INVITE to Shootme, and NOTIFYs to referer about call progress
058: *
059: * This is the referee
060: *
061: * @see RFC3515 http://www.ietf.org/rfc/rfc3515.txt
062: *
063: * @author Jeroen van Bemmel
064: * @author Ivelin Ivanov
065: *
066: */
067: public class Referee implements SipListener {
068:
069: private static AddressFactory addressFactory;
070:
071: private static MessageFactory messageFactory;
072:
073: private static HeaderFactory headerFactory;
074:
075: private static SipStack sipStack;
076:
077: public static final int myPort = 5070;
078:
079: protected SipProvider mySipProvider;
080:
081: protected Dialog dialog;
082:
083: private static Logger logger = Logger.getLogger(Referee.class);
084:
085: private EventHeader referEvent;
086:
087: private String transport;
088:
089: static {
090: try {
091: logger.setLevel(Level.INFO);
092: logger.addAppender(new FileAppender(new SimpleLayout(),
093: "logs/refereeoutputlog.txt"));
094: } catch (Exception ex) {
095: throw new RuntimeException(ex);
096: }
097: }
098:
099: public Referee(ProtocolObjects protObjects) {
100: addressFactory = protObjects.addressFactory;
101: messageFactory = protObjects.messageFactory;
102: headerFactory = protObjects.headerFactory;
103: sipStack = protObjects.sipStack;
104: transport = protObjects.transport;
105: }
106:
107: public void processRequest(RequestEvent requestEvent) {
108:
109: Request request = requestEvent.getRequest();
110: ServerTransaction serverTransactionId = requestEvent
111: .getServerTransaction();
112:
113: logger.info("\n\nRequest " + request.getMethod()
114: + " received at " + sipStack.getStackName()
115: + " with server transaction id " + serverTransactionId
116: + " and dialog id " + requestEvent.getDialog());
117: logger.info(request.toString());
118: if (request.getMethod().equals(Request.REFER)) {
119: try {
120: processRefer(requestEvent, serverTransactionId);
121: } catch (Exception e) {
122: logger.info(
123: "Referee failed processing REFER, because of "
124: + e.getMessage(), e);
125: TestHarness
126: .fail("Referee failed processing REFER, because of "
127: + e.getMessage());
128: }
129: }
130:
131: }
132:
133: /**
134: * Process the REFER request.
135: * @throws ParseException
136: * @throws SipException
137: * @throws InvalidArgumentException
138: */
139: public void processRefer(RequestEvent requestEvent,
140: ServerTransaction serverTransaction) throws ParseException,
141: SipException, InvalidArgumentException {
142: SipProvider sipProvider = (SipProvider) requestEvent
143: .getSource();
144: Request refer = requestEvent.getRequest();
145:
146: logger.info("referee: got an REFER sending Accepted");
147: logger.info("referee: " + refer.getMethod());
148: dialog = requestEvent.getDialog();
149: logger.info("referee : dialog = " + requestEvent.getDialog());
150:
151: // Check that it has a Refer-To, if not bad request
152: ReferToHeader refTo = (ReferToHeader) refer
153: .getHeader(ReferToHeader.NAME);
154: if (refTo == null) {
155: Response bad = messageFactory.createResponse(
156: Response.BAD_REQUEST, refer);
157: bad.setReasonPhrase("Missing Refer-To");
158: sipProvider.sendResponse(bad);
159: TestHarness.fail("Bad REFER request. Missing Refer-To.");
160: }
161:
162: // Always create a ServerTransaction, best as early as possible in the code
163: Response response = null;
164: ServerTransaction st = requestEvent.getServerTransaction();
165: if (st == null) {
166: st = sipProvider.getNewServerTransaction(refer);
167: }
168:
169: // Check if it is an initial SUBSCRIBE or a refresh / unsubscribe
170: String toTag = Integer
171: .toHexString((int) (Math.random() * Integer.MAX_VALUE));
172: response = messageFactory.createResponse(202, refer);
173: ToHeader toHeader = (ToHeader) response
174: .getHeader(ToHeader.NAME);
175:
176: // Sanity check: to header should not have a tag. Else the dialog
177: // should have matched
178: TestHarness
179: .assertNull(
180: "To-tag!=null but no dialog match! My dialog="
181: + dialog, toHeader.getTag());
182: toHeader.setTag(toTag); // Application is supposed to set.
183:
184: this .dialog = st.getDialog();
185: // REFER dialogs do not terminate on bye.
186: this .dialog.terminateOnBye(false);
187: if (dialog != null) {
188: logger.info("Dialog " + dialog);
189: logger.info("Dialog state " + dialog.getState());
190: logger.info("local tag=" + dialog.getLocalTag());
191: logger.info("remote tag=" + dialog.getRemoteTag());
192: }
193:
194: // Both 2xx response to SUBSCRIBE and NOTIFY need a Contact
195: Address address = addressFactory
196: .createAddress("Referee <sip:127.0.0.1>");
197: ((SipURI) address.getURI()).setPort(mySipProvider
198: .getListeningPoint(transport).getPort());
199: ContactHeader contactHeader = headerFactory
200: .createContactHeader(address);
201: response.addHeader(contactHeader);
202:
203: // Expires header is mandatory in 2xx responses to REFER
204: ExpiresHeader expires = (ExpiresHeader) refer
205: .getHeader(ExpiresHeader.NAME);
206: if (expires == null) {
207: expires = headerFactory.createExpiresHeader(30); // rather short
208: }
209: response.addHeader(expires);
210:
211: /*
212: * The REFER MUST be answered first.
213: */
214: TestHarness.assertNull(dialog.getState());
215: st.sendResponse(response);
216: TestHarness.assertEquals(DialogState.CONFIRMED, dialog
217: .getState());
218:
219: // NOTIFY MUST have "refer" event, possibly with id
220: referEvent = headerFactory.createEventHeader("refer");
221:
222: // Not necessary, but allowed: id == cseq of REFER
223: long id = ((CSeqHeader) refer.getHeader("CSeq")).getSeqNumber();
224: referEvent.setEventId(Long.toString(id));
225:
226: sendNotify(Response.TRYING, "Trying");
227:
228: // Then call the refer-to
229: sendInvite(refTo);
230: }
231:
232: private void sendNotify(int code, String reason)
233: throws SipException, ParseException {
234: /*
235: * NOTIFY requests MUST contain a "Subscription-State" header with a
236: * value of "active", "pending", or "terminated". The "active" value
237: * indicates that the subscription has been accepted and has been
238: * authorized (in most cases; see section 5.2.). The "pending" value
239: * indicates that the subscription has been received, but that
240: * policy information is insufficient to accept or deny the
241: * subscription at this time. The "terminated" value indicates that
242: * the subscription is not active.
243: */
244:
245: Request notifyRequest = dialog.createRequest("NOTIFY");
246:
247: // Initial state is pending, second time we assume terminated (Expires==0)
248: String state = SubscriptionStateHeader.PENDING;
249: if (code > 100 && code < 200) {
250: state = SubscriptionStateHeader.ACTIVE;
251: } else if (code >= 200) {
252: state = SubscriptionStateHeader.TERMINATED;
253: }
254:
255: SubscriptionStateHeader sstate = headerFactory
256: .createSubscriptionStateHeader(state);
257: if (state == SubscriptionStateHeader.TERMINATED) {
258: sstate.setReasonCode("noresource");
259: }
260: notifyRequest.addHeader(sstate);
261: notifyRequest.setHeader(referEvent);
262:
263: Address address = addressFactory
264: .createAddress("Referee <sip:127.0.0.1>");
265: ((SipURI) address.getURI()).setPort(mySipProvider
266: .getListeningPoint(transport).getPort());
267: ((SipURI) address.getURI()).setTransportParam(transport);
268: ContactHeader contactHeader = headerFactory
269: .createContactHeader(address);
270: notifyRequest.setHeader(contactHeader);
271: // notifyRequest.setHeader(routeHeader);
272: ClientTransaction ct2 = mySipProvider
273: .getNewClientTransaction(notifyRequest);
274:
275: ContentTypeHeader ct = headerFactory.createContentTypeHeader(
276: "message", "sipfrag");
277: ct.setParameter("version", "2.0");
278:
279: notifyRequest.setContent("SIP/2.0 " + code + ' ' + reason, ct);
280:
281: // Let the other side know that the tx is pending acceptance
282: //
283: dialog.sendRequest(ct2);
284: logger.info("NOTIFY Branch ID "
285: + ((ViaHeader) notifyRequest.getHeader(ViaHeader.NAME))
286: .getParameter("branch"));
287: logger.info("Dialog " + dialog);
288: logger.info("Dialog state after NOTIFY: " + dialog.getState());
289: }
290:
291: public void processResponse(ResponseEvent responseReceivedEvent) {
292: logger.info("Got a response");
293: Response response = (Response) responseReceivedEvent
294: .getResponse();
295: Transaction tid = responseReceivedEvent.getClientTransaction();
296:
297: logger.info("Response received with client transaction id "
298: + tid + ":\n" + response.getStatusCode() + " cseq = "
299: + response.getHeader(CSeqHeader.NAME));
300:
301: CSeqHeader cseq = (CSeqHeader) response
302: .getHeader(CSeqHeader.NAME);
303: if (cseq.getMethod().equals(Request.INVITE)) {
304:
305: try {
306: sendNotify(response.getStatusCode(), response
307: .getReasonPhrase());
308: } catch (Exception e1) {
309: TestHarness.fail("Failed to send notify, because of "
310: + e1.getMessage());
311: }
312:
313: if (response.getStatusCode() >= 200
314: && response.getStatusCode() < 300) {
315: try {
316: Request ack = tid.getDialog().createAck(
317: cseq.getSeqNumber());
318: tid.getDialog().sendAck(ack);
319:
320: // kill it right away
321: Request bye = tid.getDialog().createRequest(
322: Request.BYE);
323: tid.getDialog().sendRequest(
324: mySipProvider.getNewClientTransaction(bye));
325: } catch (Exception e) {
326: TestHarness
327: .fail("Failed to send BYE request, because of "
328: + e.getMessage());
329: }
330: }
331: }
332:
333: }
334:
335: public void processTimeout(javax.sip.TimeoutEvent timeoutEvent) {
336: Transaction transaction;
337: if (timeoutEvent.isServerTransaction()) {
338: transaction = timeoutEvent.getServerTransaction();
339: } else {
340: transaction = timeoutEvent.getClientTransaction();
341: }
342: logger.info("state = " + transaction.getState());
343: logger.info("dialog = " + transaction.getDialog());
344: logger.info("dialogState = "
345: + transaction.getDialog().getState());
346: logger.info("Transaction Time out");
347: }
348:
349: public void sendInvite(ReferToHeader to) {
350:
351: try {
352:
353: String fromName = "Referee";
354: String fromSipAddress = "here.com";
355: String fromDisplayName = "The Master Blaster";
356:
357: // create >From Header
358: SipURI fromAddress = addressFactory.createSipURI(fromName,
359: fromSipAddress);
360:
361: Address fromNameAddress = addressFactory
362: .createAddress(fromAddress);
363: fromNameAddress.setDisplayName(fromDisplayName);
364: FromHeader fromHeader = headerFactory.createFromHeader(
365: fromNameAddress, "12345");
366:
367: // create To Header
368: ToHeader toHeader = headerFactory.createToHeader(to
369: .getAddress(), null);
370:
371: // get Request URI
372: SipURI requestURI = (SipURI) to.getAddress().getURI();
373:
374: ListeningPoint lp = mySipProvider
375: .getListeningPoint(transport);
376:
377: // Create ViaHeaders
378:
379: ArrayList viaHeaders = new ArrayList();
380: ViaHeader viaHeader = headerFactory.createViaHeader(
381: "127.0.0.1", lp.getPort(), transport, null);
382:
383: // add via headers
384: viaHeaders.add(viaHeader);
385:
386: // Create a new CallId header
387: CallIdHeader callIdHeader = mySipProvider.getNewCallId();
388: // JvB: Make sure that the implementation matches the messagefactory
389: callIdHeader = headerFactory
390: .createCallIdHeader(callIdHeader.getCallId());
391:
392: // Create a new Cseq header
393: CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1L,
394: Request.INVITE);
395:
396: // Create a new MaxForwardsHeader
397: MaxForwardsHeader maxForwards = headerFactory
398: .createMaxForwardsHeader(70);
399:
400: // Create the request. (TODO should read request type from Refer-To)
401: Request request = messageFactory.createRequest(requestURI,
402: Request.INVITE, callIdHeader, cSeqHeader,
403: fromHeader, toHeader, viaHeaders, maxForwards);
404: // Create contact headers
405: String host = lp.getIPAddress();
406:
407: SipURI contactURI = addressFactory.createSipURI(fromName,
408: host);
409: contactURI.setPort(lp.getPort());
410: contactURI.setTransportParam(transport);
411:
412: Address contactAddress = addressFactory
413: .createAddress(contactURI);
414:
415: // Add the contact address.
416: contactAddress.setDisplayName(fromName);
417:
418: ContactHeader contactHeader = headerFactory
419: .createContactHeader(contactAddress);
420: request.addHeader(contactHeader);
421:
422: // Create the client transaction.
423: ClientTransaction inviteTid = mySipProvider
424: .getNewClientTransaction(request);
425:
426: logger.info("Invite Dialog = " + inviteTid.getDialog());
427:
428: // send the request out.
429: inviteTid.sendRequest();
430:
431: } catch (Throwable ex) {
432: TestHarness.fail("Failed to send INVITE, because of " + ex);
433: }
434: }
435:
436: public SipProvider createProvider() throws Exception {
437: ListeningPoint lp = sipStack.createListeningPoint("127.0.0.1",
438: myPort, transport);
439:
440: this .mySipProvider = sipStack.createSipProvider(lp);
441: logger.info("provider " + mySipProvider);
442:
443: return mySipProvider;
444: }
445:
446: public void processIOException(IOExceptionEvent exceptionEvent) {
447: logger.error("processIOEx:" + exceptionEvent);
448: TestHarness.fail("unexpected event");
449: }
450:
451: public void processTransactionTerminated(
452: TransactionTerminatedEvent tte) {
453:
454: logger.info("transaction terminated:" + tte);
455: }
456:
457: public void processDialogTerminated(
458: DialogTerminatedEvent dialogTerminatedEvent) {
459:
460: logger.info("dialog terminated:" + dialogTerminatedEvent);
461: }
462:
463: }
|