package test.unit.gov.nist.javax.sip.stack; import gov.nist.javax.sip.stack.NioMessageProcessorFactory; import java.util.ArrayList; import java.util.LinkedList; import java.util.Properties; import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.DialogTerminatedEvent; import javax.sip.IOExceptionEvent; import javax.sip.ListeningPoint; import javax.sip.RequestEvent; import javax.sip.ResponseEvent; import javax.sip.ServerTransaction; import javax.sip.SipFactory; import javax.sip.SipListener; import javax.sip.SipProvider; import javax.sip.SipStack; import javax.sip.Transaction; import javax.sip.TransactionTerminatedEvent; import javax.sip.address.Address; import javax.sip.address.AddressFactory; import javax.sip.address.SipURI; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.header.ContactHeader; import javax.sip.header.EventHeader; import javax.sip.header.ExpiresHeader; import javax.sip.header.FromHeader; import javax.sip.header.HeaderFactory; import javax.sip.header.MaxForwardsHeader; import javax.sip.header.RouteHeader; import javax.sip.header.SubscriptionStateHeader; import javax.sip.header.ToHeader; import javax.sip.header.ViaHeader; import javax.sip.message.MessageFactory; import javax.sip.message.Request; import javax.sip.message.Response; import junit.framework.TestCase; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.FileAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.SimpleLayout; public class DeliverNotifyBefore202Test extends TestCase { private static Logger logger = Logger.getLogger(DeliverNotifyBefore202Test.class); private static AddressFactory addressFactory; private static MessageFactory messageFactory; private static HeaderFactory headerFactory; private static SipFactory sipFactory; private Notifier notifier; private Subscriber subscriber; static { try { sipFactory = SipFactory.getInstance(); sipFactory.setPathName("gov.nist"); logger.setLevel(Level.DEBUG); logger.addAppender(new ConsoleAppender(new SimpleLayout())); logger.addAppender(new FileAppender(new SimpleLayout(), "subscriberoutputlog.txt")); sipFactory.setPathName("gov.nist"); headerFactory = sipFactory.createHeaderFactory(); addressFactory = sipFactory.createAddressFactory(); messageFactory = sipFactory.createMessageFactory(); } catch (Exception ex) { throw new RuntimeException(ex); } } /** * The Subscriber. */ class Subscriber implements SipListener { private SipProvider udpProvider; private SipStack sipStack; private ContactHeader contactHeader; private int notifierPort; private String transport = "udp"; private int count; private ClientTransaction subscribeTid; private ListeningPoint listeningPoint; private int port; private boolean notifySeen; public void processRequest(RequestEvent requestReceivedEvent) { Request request = requestReceivedEvent.getRequest(); ServerTransaction serverTransactionId = requestReceivedEvent.getServerTransaction(); String viaBranch = ((ViaHeader) (request.getHeaders(ViaHeader.NAME).next())) .getParameter("branch"); logger.info("\n\nRequest " + request.getMethod() + " received at " + sipStack.getStackName() + " with server transaction id " + serverTransactionId + " branch ID = " + viaBranch); if (request.getMethod().equals(Request.NOTIFY)) processNotify(requestReceivedEvent, serverTransactionId); } public synchronized void processNotify(RequestEvent requestEvent, ServerTransaction serverTransactionId) { SipProvider provider = (SipProvider) requestEvent.getSource(); Request notify = requestEvent.getRequest(); try { logger.info("subscriber: got a notify count " + this.count++); if (serverTransactionId == null) { logger.info("subscriber: null TID."); serverTransactionId = provider.getNewServerTransaction(notify); } Dialog dialog = serverTransactionId.getDialog(); logger.info("Dialog = " + dialog); if (dialog != null) { logger.info("Dialog State = " + dialog.getState()); } Response response = messageFactory.createResponse(200, notify); // SHOULD add a Contact ContactHeader contact = (ContactHeader) contactHeader.clone(); ((SipURI) contact.getAddress().getURI()).setParameter("id", "sub"); response.addHeader(contact); logger.info("Transaction State = " + serverTransactionId.getState()); serverTransactionId.sendResponse(response); if (dialog != null) { logger.info("Dialog State = " + dialog.getState()); } SubscriptionStateHeader subscriptionState = (SubscriptionStateHeader) notify .getHeader(SubscriptionStateHeader.NAME); // Subscription is terminated? String state = subscriptionState.getState(); if (state.equalsIgnoreCase(SubscriptionStateHeader.TERMINATED)) { dialog.delete(); } else { logger.info("Subscriber: state now " + state); } this.notifySeen = true; } catch (Exception ex) { ex.printStackTrace(); logger.error("Unexpected exception", ex); fail("Unexpected exception"); } } public void processResponse(ResponseEvent responseReceivedEvent) { logger.info("Got a response"); Response response = (Response) responseReceivedEvent.getResponse(); Transaction tid = responseReceivedEvent.getClientTransaction(); logger.info("Response received with client transaction id " + tid + ":\n" + response.getStatusCode()); if (tid == null) { logger.info("Stray response -- dropping "); return; } logger.info("transaction state is " + tid.getState()); logger.info("Dialog = " + tid.getDialog()); if (tid.getDialog() != null) logger.info("Dialog State is " + tid.getDialog().getState()); } public void createProvider() throws Exception { this.listeningPoint = sipStack.createListeningPoint("127.0.0.1", port, transport); udpProvider = sipStack.createSipProvider(listeningPoint); } public void sendSubscribe() { try { String fromName = "BigGuy"; String fromSipAddress = "here.com"; String fromDisplayName = "The Master Blaster"; String toSipAddress = "there.com"; String toUser = "LittleGuy"; String toDisplayName = "The Little Blister"; // create >From Header SipURI fromAddress = addressFactory.createSipURI(fromName, fromSipAddress); Address fromNameAddress = addressFactory.createAddress(fromAddress); fromNameAddress.setDisplayName(fromDisplayName); FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, "12345"); // create To Header SipURI toAddress = addressFactory.createSipURI(toUser, toSipAddress); Address toNameAddress = addressFactory.createAddress(toAddress); toNameAddress.setDisplayName(toDisplayName); ToHeader toHeader = headerFactory.createToHeader(toNameAddress, null); // create Request URI SipURI requestURI = addressFactory.createSipURI(toUser, toSipAddress); // Create ViaHeaders ArrayList viaHeaders = new ArrayList(); int port = udpProvider.getListeningPoint(transport).getPort(); ViaHeader viaHeader = headerFactory.createViaHeader("127.0.0.1", port, transport, null); // add via headers viaHeaders.add(viaHeader); // Create a new CallId header CallIdHeader callIdHeader = udpProvider.getNewCallId(); // Create a new Cseq header CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1L, Request.SUBSCRIBE); // Create a new MaxForwardsHeader MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70); // Create the request. Request request = messageFactory.createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); // Create contact headers String host = listeningPoint.getIPAddress(); SipURI contactUrl = addressFactory.createSipURI(fromName, host); contactUrl.setPort(listeningPoint.getPort()); // Create the contact name address. SipURI contactURI = addressFactory.createSipURI(fromName, host); contactURI.setTransportParam(transport); contactURI.setPort(udpProvider.getListeningPoint(transport).getPort()); Address contactAddress = addressFactory.createAddress(contactURI); // Add the contact address. contactAddress.setDisplayName(fromName); contactHeader = headerFactory.createContactHeader(contactAddress); request.addHeader(contactHeader); // JvB: To test forked SUBSCRIBEs, send it via the Forker // Note: BIG Gotcha: Need to do this before creating the // ClientTransaction! RouteHeader route = headerFactory.createRouteHeader(addressFactory .createAddress("<sip:127.0.0.1:" + notifierPort + ";transport=" + transport + ";lr>")); request.addHeader(route); // JvB end added // Create the client transaction. subscribeTid = udpProvider.getNewClientTransaction(request); // Create an event header for the subscription. EventHeader eventHeader = headerFactory.createEventHeader("foo"); eventHeader.setEventId("foo"); request.addHeader(eventHeader); logger.info("Subscribe Dialog = " + subscribeTid.getDialog()); // send the request out. subscribeTid.sendRequest(); } catch (Throwable ex) { logger.info(ex.getMessage()); ex.printStackTrace(); fail("Unexpected exception"); } } public void processIOException(IOExceptionEvent exceptionEvent) { logger.info("io exception event recieved"); fail ("unexpected event -- IOException"); } public void processTransactionTerminated( TransactionTerminatedEvent transactionTerminatedEvent) { } public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { logger.info("dialog terminated event recieved"); } public void processTimeout(javax.sip.TimeoutEvent timeoutEvent) { logger.info("Transaction Time out"); fail ("Unexpected event -- timeout"); } public Subscriber(int notifierPort, int port) throws Exception { this.notifierPort = notifierPort; this.port = port; logger.addAppender(new FileAppender(new SimpleLayout(), "subscriberoutputlog_" + port + ".txt")); Properties properties = new Properties(); properties.setProperty("javax.sip.STACK_NAME", "subscriber" + port); // You need 16 for logging traces. 32 for debug + traces. // Your code will limp at 32 but it is best for debugging. properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32"); properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "subscriberdebug_" + port + ".txt"); properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "subscriberlog_" + port + ".txt"); if(System.getProperty("enableNIO") != null && System.getProperty("enableNIO").equalsIgnoreCase("true")) { logger.info("\nNIO Enabled\n"); properties.setProperty("gov.nist.javax.sip.MESSAGE_PROCESSOR_FACTORY", NioMessageProcessorFactory.class.getName()); } // Create SipStack object sipStack = sipFactory.createSipStack(properties); logger.info("sipStack = " + sipStack); this.createProvider( ); this.udpProvider.addSipListener(this); } public void tearDown() { this.sipStack.stop(); } public boolean checkNotify() { return this.notifySeen; } } /** * The Notifier */ class Notifier implements SipListener { private SipStack sipStack; private int port; private SipProvider udpProvider; private Dialog dialog; protected int notifyCount = 0; public void processRequest(RequestEvent requestEvent) { Request request = requestEvent.getRequest(); ServerTransaction serverTransactionId = requestEvent.getServerTransaction(); logger.info("\n\nRequest " + request.getMethod() + " received at " + sipStack.getStackName() + " with server transaction id " + serverTransactionId + " and dialog id " + requestEvent.getDialog()); if (request.getMethod().equals(Request.SUBSCRIBE)) { processSubscribe(requestEvent, serverTransactionId); } } /** * Process the invite request. */ public void processSubscribe(RequestEvent requestEvent, ServerTransaction serverTransaction) { SipProvider sipProvider = (SipProvider) requestEvent.getSource(); Request request = requestEvent.getRequest(); try { logger.info("notifier: got an Subscribe sending OK"); logger.info("notifier: " + request); logger.info("notifier : dialog = " + requestEvent.getDialog()); EventHeader eventHeader = (EventHeader) request.getHeader(EventHeader.NAME); if (eventHeader == null) { logger.info("Cannot find event header.... dropping request."); return; } // Always create a ServerTransaction, best as early as possible in the code Response response = null; ServerTransaction st = requestEvent.getServerTransaction(); if (st == null) { st = sipProvider.getNewServerTransaction(request); } // Check if it is an initial SUBSCRIBE or a refresh / unsubscribe boolean isInitial = requestEvent.getDialog() == null; if (isInitial) { // JvB: need random tags to test forking String toTag = Integer.toHexString((int) (Math.random() * Integer.MAX_VALUE)); response = messageFactory.createResponse(202, request); ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); // Sanity check: to header should not ahve a tag. Else the dialog // should have matched if (toHeader.getTag() != null) { System.err .println("####ERROR: To-tag!=null but no dialog match! My dialog=" + dialog.getState()); } toHeader.setTag(toTag); // Application is supposed to set. this.dialog = st.getDialog(); // subscribe dialogs do not terminate on bye. this.dialog.terminateOnBye(false); if (dialog != null) { logger.info("Dialog " + dialog); logger.info("Dialog state " + dialog.getState()); } } else { response = messageFactory.createResponse(200, request); } // Both 2xx response to SUBSCRIBE and NOTIFY need a Contact Address address = addressFactory.createAddress("Notifier <sip:127.0.0.1>"); ((SipURI) address.getURI()).setPort(udpProvider.getListeningPoint("udp") .getPort()); ContactHeader contactHeader = headerFactory.createContactHeader(address); response.addHeader(contactHeader); // Expires header is mandatory in 2xx responses to SUBSCRIBE ExpiresHeader expires = (ExpiresHeader) request.getHeader(ExpiresHeader.NAME); if (expires == null) { expires = headerFactory.createExpiresHeader(30); // rather short } response.addHeader(expires); /* * NOTIFY requests MUST contain a "Subscription-State" header with a value of * "active", "pending", or "terminated". The "active" value indicates that the * subscription has been accepted and has been authorized (in most cases; see * section 5.2.). The "pending" value indicates that the subscription has been * received, but that policy information is insufficient to accept or deny the * subscription at this time. The "terminated" value indicates that the * subscription is not active. */ Address fromAddress = ((ToHeader) response.getHeader(ToHeader.NAME)).getAddress(); String fromTag = ((ToHeader) response.getHeader(ToHeader.NAME)).getTag(); FromHeader fromHeader = headerFactory.createFromHeader(fromAddress, fromTag); Address toAddress = ((FromHeader) response.getHeader(FromHeader.NAME)).getAddress(); String toTag = ((FromHeader) response.getHeader(FromHeader.NAME)).getTag(); ToHeader toHeader = headerFactory.createToHeader(toAddress, toTag); CallIdHeader callId = (CallIdHeader) response.getHeader(CallIdHeader.NAME); ContactHeader requestContact = (ContactHeader) request .getHeader(ContactHeader.NAME); SipURI notifyRuri = (SipURI) requestContact.getAddress().getURI(); CSeqHeader cSeq = headerFactory.createCSeqHeader(1L, Request.NOTIFY); String ipAddress = sipProvider.getListeningPoint("udp").getIPAddress(); int port = sipProvider.getListeningPoint("udp").getPort(); ViaHeader viaHeader = headerFactory.createViaHeader(ipAddress, port, "udp", null); LinkedList llist = new LinkedList<ViaHeader>(); llist.add(viaHeader); MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70); Request notifyRequest = messageFactory.createRequest(notifyRuri, Request.NOTIFY, callId, cSeq, fromHeader, toHeader, llist, maxForwards); notifyRequest.addHeader(contactHeader); // Mark the contact header, to check that the remote contact is updated ((SipURI) contactHeader.getAddress().getURI()).setParameter("id", "not"); // Initial state is pending, second time we assume terminated (Expires==0) SubscriptionStateHeader sstate = headerFactory .createSubscriptionStateHeader(isInitial ? SubscriptionStateHeader.PENDING : SubscriptionStateHeader.TERMINATED); // Need a reason for terminated if (sstate.getState().equalsIgnoreCase("terminated")) { sstate.setReasonCode("deactivated"); } notifyRequest.addHeader(sstate); notifyRequest.setHeader(eventHeader); notifyRequest.setHeader(contactHeader); // notifyRequest.setHeader(routeHeader); ClientTransaction ct = udpProvider.getNewClientTransaction(notifyRequest); /* * We deliberately send the NOTIFY first before the 202 is sent. */ ct.sendRequest(); logger.info("NOTIFY Branch ID " + ((ViaHeader) request.getHeader(ViaHeader.NAME)).getParameter("branch")); logger.info("Dialog " + dialog); logger.info("Dialog state after pending NOTIFY: " + dialog.getState()); /* * Now send the NOTIFY. */ st.sendResponse(response); } catch (Throwable ex) { ex.printStackTrace(); // System.exit(0); } } public synchronized void processResponse(ResponseEvent responseReceivedEvent) { Response response = (Response) responseReceivedEvent.getResponse(); Transaction tid = responseReceivedEvent.getClientTransaction(); if (response.getStatusCode() != 200) { this.notifyCount--; } else { System.out.println("Notify Count = " + this.notifyCount); } } public void processTimeout(javax.sip.TimeoutEvent timeoutEvent) { Transaction transaction; if (timeoutEvent.isServerTransaction()) { transaction = timeoutEvent.getServerTransaction(); } else { transaction = timeoutEvent.getClientTransaction(); } logger.info("state = " + transaction.getState()); logger.info("dialog = " + transaction.getDialog()); logger.info("dialogState = " + transaction.getDialog().getState()); logger.info("Transaction Time out"); } public void createProvider() { try { ListeningPoint lp = sipStack.createListeningPoint("127.0.0.1", this.port, "udp"); this.udpProvider = sipStack.createSipProvider(lp); logger.info("udp provider " + udpProvider); } catch (Exception ex) { logger.info(ex.getMessage()); ex.printStackTrace(); } } public Notifier(int port) throws Exception { this.port = port; Properties properties = new Properties(); logger.addAppender(new FileAppender(new SimpleLayout(), "notifieroutputlog_" + port + ".txt")); properties.setProperty("javax.sip.STACK_NAME", "notifier" + port); // You need 16 for logging traces. 32 for debug + traces. // Your code will limp at 32 but it is best for debugging. properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32"); properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "notifierdebug_" + port + ".txt"); properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "notifierlog_" + port + ".txt"); if(System.getProperty("enableNIO") != null && System.getProperty("enableNIO").equalsIgnoreCase("true")) { logger.info("\nNIO Enabled\n"); properties.setProperty("gov.nist.javax.sip.MESSAGE_PROCESSOR_FACTORY", NioMessageProcessorFactory.class.getName()); } // Create SipStack object sipStack = sipFactory.createSipStack(properties); logger.info("sipStack = " + sipStack); this.createProvider( ); this.udpProvider.addSipListener(this); } public void processIOException(IOExceptionEvent exceptionEvent) { } public void processTransactionTerminated( TransactionTerminatedEvent transactionTerminatedEvent) { } public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { // TODO Auto-generated method stub } public void tearDown() { this.sipStack.stop(); } } public void setUp() throws Exception { this.notifier = new Notifier(5090); this.subscriber = new Subscriber(5090,5092); } public void testDeliverNotifyBefore202() { this.subscriber.sendSubscribe(); try { Thread.sleep(2000); } catch (Exception ex) { } if ( ! this.subscriber.checkNotify() ) { fail("Notify not received"); } } public void tearDown() { this.subscriber.tearDown(); this.notifier.tearDown(); } }