All Downloads are FREE. Search and download functionalities are using the official Maven repository.

test.tck.msgflow.callflows.subsnotify.Forker Maven / Gradle / Ivy

/*
* Conditions Of Use 
* 
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), and others. 
* This software is has been contributed to the public domain. 
* As a result, a formal license is not needed to use the software.
* 
* This software is provided "AS IS."  
* NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY.  NIST does not warrant or make any representations
* regarding the use of the software or the results thereof, including but
* not limited to the correctness, accuracy, reliability or usefulness of
* the software.
* 
* 
*/
package test.tck.msgflow.callflows.subsnotify;

import javax.sip.*;
import javax.sip.address.*;
import javax.sip.header.*;
import javax.sip.message.*;

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;

import test.tck.msgflow.callflows.ProtocolObjects;

import java.text.ParseException;

import java.util.*;

/**
 * This implements a simple forking proxy to test proper handling of multiple
 * NOTIFYs. An initial SUBSCRIBE request (i.e. without to-tag) is forked two
 * times to the same destination. Each response should have a different to-tag;
 * this proxy only passes the first 2xx response through, and discards the
 * second
 * 
 * NOTIFYs should go directly to the Contact announced in the SUBSCRIBE, hence
 * this proxy won't see them
 * 
 *
 * @author Jeroen van Bemmel
 */

public class Forker implements SipListener {

	private static AddressFactory addressFactory;

	private static MessageFactory messageFactory;

	private static HeaderFactory headerFactory;

	private static SipStack sipStack;

	private SipProvider sipProvider;
	
	private Hashtable serverTransactionTable = new Hashtable();

	/**
	 * Flag to test UAC behavior for non-RFC3261 proxies. In particular, they
	 * dont set the 'lr' flag and perform strict routing, ie replace the request
	 * URI with the topmost Route header
	 */
	private static boolean nonRFC3261Proxy;

	private static Logger logger = Logger.getLogger(Forker.class);
	static {
		try {
			logger.addAppender(new FileAppender(new SimpleLayout(),
					"logs/forkeroutputlog.txt"));
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}

	/**
	 * Adds a suitable Record-Route header to the given request or response
	 * 
	 * @param r
	 * @throws ParseException
	 * @throws SipException
	 * @throws
	 */
	private void recordRoute(Message m, String uniqueId) throws ParseException,
			SipException {
		Address me = addressFactory.createAddress(" forking or forwarding");

			// Check if it is in-dialog or not
			ToHeader to = (ToHeader) request.getHeader(ToHeader.NAME);
			if (to.getTag() == null) {
				logger
						.info("forker: got a dialog-creating Subscribe forking twice");

				if (st == null) {
					st = ((SipProvider) re.getSource())
							.getNewServerTransaction(request);
				}

				AbstractSubsnotifyTestCase.assertTrue(
						"Proxy: dialog stateless operation expected", st
								.getDialog() == null);
				// Subscriber added a Route to us; remove it could check its
				// 'id' here)
				Request newRequest = (Request) request.clone();
				newRequest.removeFirst(RouteHeader.NAME);

				doFork(newRequest, st, 5070);
				doFork(newRequest, st, 5071);
			} else {
				logger
						.info("forker: got a mid-dialog Subscribe, forwarding statelessly...");

				// Forward it statelessly
				Request newRequest = (Request) request.clone();
				doForwardStateless(newRequest, st);
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			logger.error("unexpected exception", ex);
			AbstractSubsnotifyTestCase.fail("unexpected exception");
		}
	}

	/**
	 * Mapping of Via branch IDs to the corresponding ServerTransaction, used
	 * for forwarding responses
	 */
	private final Map CTtoST = new HashMap();

	private String transport;

	private int port;

	public Forker(ProtocolObjects protObjects) {
		addressFactory = protObjects.addressFactory;
		messageFactory = protObjects.messageFactory;
		headerFactory = protObjects.headerFactory;
		sipStack = protObjects.sipStack;
		transport = protObjects.transport;
	}

	private void doFork(Request orig, ServerTransaction st, int port)
			throws Exception {
		ViaHeader myVia = headerFactory.createViaHeader("127.0.0.1",
				sipProvider.getListeningPoint("udp").getPort(), "udp", null);
		Request forked = (Request) orig.clone();
		forked.addHeader(myVia);

		// Note: BIG Gotcha: Need to do this before creating the
		// ClientTransaction!
		if (nonRFC3261Proxy) {
			SipURI suri = addressFactory.createSipURI(null, "127.0.0.1");
			suri.setPort(port);
			forked.setRequestURI(suri);
		} else {
			RouteHeader route = headerFactory.createRouteHeader(addressFactory
					.createAddress(""));
			((SipURI) route.getAddress().getURI()).setPort(port);
			forked.addHeader(route);
		}

		// Add a Record-Route header, to test that separate dialog instances are
		// correctly created at the subscriber
		// This causes us to receive NOTIFYs too
		recordRoute(forked, Integer.toString(port));

		ClientTransaction ct = sipProvider.getNewClientTransaction(forked);
		AbstractSubsnotifyTestCase.assertTrue("Stateless operation -- should not create a dialog ", ct.getDialog() == null );
		CTtoST.put(ct, st);
		ct.sendRequest(); // gets sent to the outbound proxy == Notifier
	}

	private void doForwardStateless(Request orig, ServerTransaction st)
			throws ParseException, InvalidArgumentException, SipException {
		// To forward statelessly, we need to keep the stack from
		// creating a ST for us.
		// Internally a dialog is created for the SUBSCRIBE, unless
		// dialog support
		// XXX bug: if I use branch==null here, the stack assigns a random int
		// without magic cookie
		//
		// Latest wisdom from RFC3261 says to simply copy branch from current
		// top via
		// when forwarding statelessly
		//
		ViaHeader top = (ViaHeader) orig.getHeader(ViaHeader.NAME);
		ViaHeader myVia = headerFactory.createViaHeader("127.0.0.1", port,
				"udp", top.getBranch());
		orig.addFirst(myVia);

		if (nonRFC3261Proxy) {
			RouteHeader route = (RouteHeader) orig.getHeader("Route");
			if (route != null) {
				orig.removeFirst("Route");
				orig.setRequestURI(route.getAddress().getURI());
			}
		} else {
			orig.removeFirst(RouteHeader.NAME); // points at us
		}

		// To forward statelessly, we need to keep the stack from creating a ST
		// for us.
		// Internally a dialog is created for the SUBSCRIBE, unless dialog
		// support
		// is switched off (see initialization)
		if (st != null) {
			logger
					.info("Would like to forward statelessly, but ST!=null! Problem...");
			logger.info("st == " + st);

		}
		sipProvider.sendRequest(orig);
	}

	public void processResponse(ResponseEvent responseReceivedEvent) {
		logger.info("Got a response");
		Response response = (Response) responseReceivedEvent.getResponse().clone();
		ClientTransaction ct = responseReceivedEvent.getClientTransaction();

		logger.info("Dialog = " + responseReceivedEvent.getDialog());

		logger.info("Response received with client transaction id " + ct + ": "
				+ response.getStatusCode());

		if (ct == null) {
			logger.info("Assuming NOTIFY response, forwarding...");
			// NOTIFYs are forwarded without transaction, do the same for their
			// responses
			response.removeFirst(ViaHeader.NAME);
			try {
				String branchId = ((ViaHeader) response.getHeader(ViaHeader.NAME)).getBranch();
				ServerTransaction st = this.serverTransactionTable.get(branchId);
				if ( st != null) {
					st.sendResponse(response);
					this.serverTransactionTable.remove(branchId);
				} else {
					sipProvider.sendResponse(response);
				}
			} catch (SipException e) {
				AbstractSubsnotifyTestCase.fail("Unexpected exception seen",e);
			} catch (InvalidArgumentException ex) {
				AbstractSubsnotifyTestCase.fail("Unexpected exception seen",ex);
				
			}
		} else {
			ServerTransaction st = (ServerTransaction) CTtoST.get(ct);
			if (st != null) {
				// only forward the first response
				synchronized (st) {
					if (st.getState() == TransactionState.TRYING) {
						response.removeFirst(ViaHeader.NAME);
						try {
							st.sendResponse(response);
						} catch (SipException e) {
							e.printStackTrace();
						} catch (InvalidArgumentException e) {
							e.printStackTrace();
						}
					} else {
						logger.info("Discarding second response");
					}
					CTtoST.remove(ct);
				}
			} else {
				logger.info("No ST found");
			}
		}
	}

	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("Transaction Time out");
	}

	public SipProvider createProvider(int newPort) {

		try {

			this.port = newPort;

			ListeningPoint lp = sipStack.createListeningPoint("127.0.0.1",
					port, transport);

			this.sipProvider = sipStack.createSipProvider(lp);
			this.sipProvider.setAutomaticDialogSupportEnabled(false);
			logger.info("sip provider " + sipProvider);

		} catch (Exception ex) {
			logger.error(ex.getMessage());
			ex.printStackTrace();
			sipProvider = null;
		}

		return sipProvider;

	}

	public void processIOException(IOExceptionEvent exceptionEvent) {

	}

	public void processTransactionTerminated(
			TransactionTerminatedEvent transactionTerminatedEvent) {
		// TODO Auto-generated method stub

	}

	public void processDialogTerminated(
			DialogTerminatedEvent dialogTerminatedEvent) {
		// TODO Auto-generated method stub

	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy