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.callflows.subsnotify;
021:
022: import javax.sip.*;
023: import javax.sip.address.*;
024: import javax.sip.header.*;
025: import javax.sip.message.*;
026:
027: import org.apache.log4j.ConsoleAppender;
028: import org.apache.log4j.FileAppender;
029: import org.apache.log4j.Level;
030: import org.apache.log4j.Logger;
031: import org.apache.log4j.SimpleLayout;
032:
033: import test.tck.msgflow.callflows.ProtocolObjects;
034:
035: import java.text.ParseException;
036:
037: import java.util.*;
038:
039: /**
040: * This implements a simple forking proxy to test proper handling of multiple
041: * NOTIFYs. An initial SUBSCRIBE request (i.e. without to-tag) is forked two
042: * times to the same destination. Each response should have a different to-tag;
043: * this proxy only passes the first 2xx response through, and discards the
044: * second
045: *
046: * NOTIFYs should go directly to the Contact announced in the SUBSCRIBE, hence
047: * this proxy won't see them
048: *
049: *
050: * @author Jeroen van Bemmel
051: */
052:
053: public class Forker implements SipListener {
054:
055: private static AddressFactory addressFactory;
056:
057: private static MessageFactory messageFactory;
058:
059: private static HeaderFactory headerFactory;
060:
061: private static SipStack sipStack;
062:
063: private SipProvider sipProvider;
064:
065: private Hashtable<String, ServerTransaction> serverTransactionTable = new Hashtable<String, ServerTransaction>();
066:
067: /**
068: * Flag to test UAC behavior for non-RFC3261 proxies. In particular, they
069: * dont set the 'lr' flag and perform strict routing, ie replace the request
070: * URI with the topmost Route header
071: */
072: private static boolean nonRFC3261Proxy;
073:
074: private static Logger logger = Logger.getLogger(Forker.class);
075: static {
076: try {
077: logger.addAppender(new FileAppender(new SimpleLayout(),
078: "logs/forkeroutputlog.txt"));
079: } catch (Exception ex) {
080: throw new RuntimeException(ex);
081: }
082: }
083:
084: /**
085: * Adds a suitable Record-Route header to the given request or response
086: *
087: * @param r
088: * @throws ParseException
089: * @throws SipException
090: * @throws
091: */
092: private void recordRoute(Message m, String uniqueId)
093: throws ParseException, SipException {
094: Address me = addressFactory.createAddress("<sip:127.0.0.1:"
095: + port + ";id=" + uniqueId + '>');
096: if (!nonRFC3261Proxy)
097: ((SipURI) me.getURI()).setLrParam();
098: RecordRouteHeader rr = headerFactory
099: .createRecordRouteHeader(me);
100: m.addFirst(rr);
101: }
102:
103: public void processRequest(RequestEvent re) {
104: Request request = re.getRequest();
105: ServerTransaction st = re.getServerTransaction();
106:
107: logger.info("\n\nRequest " + request.getMethod()
108: + " received at " + sipStack.getStackName()
109: + " with server transaction id " + st);
110:
111: try {
112: if (request.getMethod().equals(Request.SUBSCRIBE)) {
113: processSubscribe(re, st);
114: } else if (request.getMethod().equals(Request.NOTIFY)) { // because
115: // of
116: // Record-Routing
117:
118: logger.info("Got NOTIFY, forwarding statelessly...");
119: Request newRequest = (Request) request.clone();
120:
121: // Forward it without creating a transaction
122:
123: // RFC3265 says: "proxy MUST record-route the initial SUBSCRIBE
124: // and
125: // any dialog-establishing NOTIFY requests
126: // Use from tag as unique id, for debugging
127: FromHeader from = (FromHeader) newRequest
128: .getHeader(FromHeader.NAME);
129: recordRoute(newRequest, from.getTag());
130: if (st != null)
131: this .serverTransactionTable.put(
132: ((ViaHeader) request
133: .getHeader(ViaHeader.NAME))
134: .getBranch(), st);
135: doForwardStateless(newRequest, st);
136: } else {
137: Response notImplemented = messageFactory
138: .createResponse(Response.NOT_IMPLEMENTED,
139: request);
140: ((SipProvider) re.getSource())
141: .sendResponse(notImplemented);
142: }
143: } catch (Exception e) {
144: e.printStackTrace();
145: }
146: }
147:
148: /**
149: * Process the invite request.
150: */
151: public void processSubscribe(RequestEvent re, ServerTransaction st) {
152: Request request = (Request) re.getRequest();
153: try {
154: logger
155: .info("forker: got an Subscribe -> forking or forwarding");
156:
157: // Check if it is in-dialog or not
158: ToHeader to = (ToHeader) request.getHeader(ToHeader.NAME);
159: if (to.getTag() == null) {
160: logger
161: .info("forker: got a dialog-creating Subscribe forking twice");
162:
163: if (st == null) {
164: st = ((SipProvider) re.getSource())
165: .getNewServerTransaction(request);
166: }
167:
168: AbstractSubsnotifyTestCase.assertTrue(
169: "Proxy: dialog stateless operation expected",
170: st.getDialog() == null);
171: // Subscriber added a Route to us; remove it could check its
172: // 'id' here)
173: Request newRequest = (Request) request.clone();
174: newRequest.removeFirst(RouteHeader.NAME);
175:
176: doFork(newRequest, st, 5070);
177: doFork(newRequest, st, 5071);
178: } else {
179: logger
180: .info("forker: got a mid-dialog Subscribe, forwarding statelessly...");
181:
182: // Forward it statelessly
183: Request newRequest = (Request) request.clone();
184: doForwardStateless(newRequest, st);
185: }
186: } catch (Exception ex) {
187: ex.printStackTrace();
188: logger.error("unexpected exception", ex);
189: AbstractSubsnotifyTestCase.fail("unexpected exception");
190: }
191: }
192:
193: /**
194: * Mapping of Via branch IDs to the corresponding ServerTransaction, used
195: * for forwarding responses
196: */
197: private final Map CTtoST = new HashMap();
198:
199: private String transport;
200:
201: private int port;
202:
203: public Forker(ProtocolObjects protObjects) {
204: addressFactory = protObjects.addressFactory;
205: messageFactory = protObjects.messageFactory;
206: headerFactory = protObjects.headerFactory;
207: sipStack = protObjects.sipStack;
208: transport = protObjects.transport;
209: }
210:
211: private void doFork(Request orig, ServerTransaction st, int port)
212: throws Exception {
213: ViaHeader myVia = headerFactory.createViaHeader("127.0.0.1",
214: sipProvider.getListeningPoint("udp").getPort(), "udp",
215: null);
216: Request forked = (Request) orig.clone();
217: forked.addHeader(myVia);
218:
219: // Note: BIG Gotcha: Need to do this before creating the
220: // ClientTransaction!
221: if (nonRFC3261Proxy) {
222: SipURI suri = addressFactory
223: .createSipURI(null, "127.0.0.1");
224: suri.setPort(port);
225: forked.setRequestURI(suri);
226: } else {
227: RouteHeader route = headerFactory
228: .createRouteHeader(addressFactory
229: .createAddress("<sip:127.0.0.1;lr>"));
230: ((SipURI) route.getAddress().getURI()).setPort(port);
231: forked.addHeader(route);
232: }
233:
234: // Add a Record-Route header, to test that separate dialog instances are
235: // correctly created at the subscriber
236: // This causes us to receive NOTIFYs too
237: recordRoute(forked, Integer.toString(port));
238:
239: ClientTransaction ct = sipProvider
240: .getNewClientTransaction(forked);
241: AbstractSubsnotifyTestCase.assertTrue(
242: "Stateless operation -- should not create a dialog ",
243: ct.getDialog() == null);
244: CTtoST.put(ct, st);
245: ct.sendRequest(); // gets sent to the outbound proxy == Notifier
246: }
247:
248: private void doForwardStateless(Request orig, ServerTransaction st)
249: throws ParseException, InvalidArgumentException,
250: SipException {
251: // To forward statelessly, we need to keep the stack from
252: // creating a ST for us.
253: // Internally a dialog is created for the SUBSCRIBE, unless
254: // dialog support
255: // XXX bug: if I use branch==null here, the stack assigns a random int
256: // without magic cookie
257: //
258: // Latest wisdom from RFC3261 says to simply copy branch from current
259: // top via
260: // when forwarding statelessly
261: //
262: ViaHeader top = (ViaHeader) orig.getHeader(ViaHeader.NAME);
263: ViaHeader myVia = headerFactory.createViaHeader("127.0.0.1",
264: port, "udp", top.getBranch());
265: orig.addFirst(myVia);
266:
267: if (nonRFC3261Proxy) {
268: RouteHeader route = (RouteHeader) orig.getHeader("Route");
269: if (route != null) {
270: orig.removeFirst("Route");
271: orig.setRequestURI(route.getAddress().getURI());
272: }
273: } else {
274: orig.removeFirst(RouteHeader.NAME); // points at us
275: }
276:
277: // To forward statelessly, we need to keep the stack from creating a ST
278: // for us.
279: // Internally a dialog is created for the SUBSCRIBE, unless dialog
280: // support
281: // is switched off (see initialization)
282: if (st != null) {
283: logger
284: .info("Would like to forward statelessly, but ST!=null! Problem...");
285: logger.info("st == " + st);
286:
287: }
288: sipProvider.sendRequest(orig);
289: }
290:
291: public void processResponse(ResponseEvent responseReceivedEvent) {
292: logger.info("Got a response");
293: Response response = (Response) responseReceivedEvent
294: .getResponse().clone();
295: ClientTransaction ct = responseReceivedEvent
296: .getClientTransaction();
297:
298: logger.info("Dialog = " + responseReceivedEvent.getDialog());
299:
300: logger.info("Response received with client transaction id "
301: + ct + ": " + response.getStatusCode());
302:
303: if (ct == null) {
304: logger.info("Assuming NOTIFY response, forwarding...");
305: // NOTIFYs are forwarded without transaction, do the same for their
306: // responses
307: response.removeFirst(ViaHeader.NAME);
308: try {
309: String branchId = ((ViaHeader) response
310: .getHeader(ViaHeader.NAME)).getBranch();
311: ServerTransaction st = this .serverTransactionTable
312: .get(branchId);
313: if (st != null) {
314: st.sendResponse(response);
315: this .serverTransactionTable.remove(branchId);
316: } else {
317: sipProvider.sendResponse(response);
318: }
319: } catch (SipException e) {
320: AbstractSubsnotifyTestCase.fail(
321: "Unexpected exception seen", e);
322: } catch (InvalidArgumentException ex) {
323: AbstractSubsnotifyTestCase.fail(
324: "Unexpected exception seen", ex);
325:
326: }
327: } else {
328: ServerTransaction st = (ServerTransaction) CTtoST.get(ct);
329: if (st != null) {
330: // only forward the first response
331: synchronized (st) {
332: if (st.getState() == TransactionState.TRYING) {
333: response.removeFirst(ViaHeader.NAME);
334: try {
335: st.sendResponse(response);
336: } catch (SipException e) {
337: e.printStackTrace();
338: } catch (InvalidArgumentException e) {
339: e.printStackTrace();
340: }
341: } else {
342: logger.info("Discarding second response");
343: }
344: CTtoST.remove(ct);
345: }
346: } else {
347: logger.info("No ST found");
348: }
349: }
350: }
351:
352: public void processTimeout(javax.sip.TimeoutEvent timeoutEvent) {
353: Transaction transaction;
354: if (timeoutEvent.isServerTransaction()) {
355: transaction = timeoutEvent.getServerTransaction();
356: } else {
357: transaction = timeoutEvent.getClientTransaction();
358: }
359: logger.info("state = " + transaction.getState());
360: logger.info("Transaction Time out");
361: }
362:
363: public SipProvider createProvider(int newPort) {
364:
365: try {
366:
367: this .port = newPort;
368:
369: ListeningPoint lp = sipStack.createListeningPoint(
370: "127.0.0.1", port, transport);
371:
372: this .sipProvider = sipStack.createSipProvider(lp);
373: this .sipProvider.setAutomaticDialogSupportEnabled(false);
374: logger.info("sip provider " + sipProvider);
375:
376: } catch (Exception ex) {
377: logger.error(ex.getMessage());
378: ex.printStackTrace();
379: sipProvider = null;
380: }
381:
382: return sipProvider;
383:
384: }
385:
386: public void processIOException(IOExceptionEvent exceptionEvent) {
387:
388: }
389:
390: public void processTransactionTerminated(
391: TransactionTerminatedEvent transactionTerminatedEvent) {
392: // TODO Auto-generated method stub
393:
394: }
395:
396: public void processDialogTerminated(
397: DialogTerminatedEvent dialogTerminatedEvent) {
398: // TODO Auto-generated method stub
399:
400: }
401:
402: }
|