/*
* RequestForwarding.java
*
* Created on April 16, 2003, 11:08 AM
*/
package org.cafesip.jiplet.sip;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Vector;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogState;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.Transaction;
import javax.sip.TransactionState;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.Header;
import javax.sip.header.HeaderFactory;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.RecordRouteHeader;
import javax.sip.header.RouteHeader;
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 org.cafesip.jiplet.Jiplet;
import org.cafesip.jiplet.JipletDialog;
import org.cafesip.jiplet.JipletException;
import org.cafesip.jiplet.JipletLogger;
/**
*
* @author Deruelle
*/
public class RequestForwarding
{
private Jiplet jiplet;
private RequestEvent requestEvent;
private ServerTransaction serverTransaction;
private boolean stateful;
private boolean addRecordRoute;
private SipCommunicator proxy;
/** Creates a new instance of RequestForwarding */
public RequestForwarding(Jiplet jiplet, SipCommunicator proxy,
RequestEvent requestEvent,
ServerTransaction serverTransaction, boolean stateful,
boolean addRecordRoute)
{
this.jiplet = jiplet;
this.proxy = proxy;
this.requestEvent = requestEvent;
this.serverTransaction = serverTransaction;
this.stateful = stateful;
this.addRecordRoute = addRecordRoute;
}
public void forwardRequest(ArrayList targetsURIList)
throws InvalidArgumentException, JipletException, ParseException,
SipException
{
/*
* RFC 3261: 16.6. Request Forwarding For each target, the jiplet
* forwards the request following these steps:
*
* 1. Make a copy of the received request
*
* 2. Update the Request-URI
*
* 3. Update the Max-Forwards header field
*
* 4. Optionally add a Record-route header field value
*
* 5. Optionally add additional header fields
*
* 6. Postprocess routing information
*
* 7. Determine the next-hop address, port, and transport
*
* 8. Add a Via header field value
*
* 9. Add a Content-Length header field if necessary
*
* 10. Forward the new request
*
* 11. Set timer C
*/
HeaderFactory headerFactory = jiplet.getHeaderFactory();
AddressFactory addressFactory = jiplet.getAddressFactory();
// Get the parameters and the transport of the request URI
URI requestURI = requestEvent.getRequest().getRequestURI();
Iterator parametersNames = null;
if (requestURI.isSipURI())
{
parametersNames = ((SipURI) requestURI).getParameterNames();
// this is how the JAIN-SIP jiplet guys have done it but it seems
// problematic. Why?
// transport = ((SipURI) requestURI).getTransportParam();
}
for (int i = 0; i < targetsURIList.size(); i++)
{
URI targetURI = (URI) targetsURIList.get(i);
// Copy the parameters and the transport in the new Request URI
// of the cloned Request
//
// 1. Make a clone of the received request
//
//
Request clonedRequest = (Request) requestEvent.getRequest().clone();
//
// 2. Update the Request-URI
//
/*
* The Request-URI in the copy's start line MUST be replaced with
* the URI for this target. If the URI contains any parameters not
* allowed in a Request-URI, they MUST be removed.
*
* This is the essence of a jiplet's role. This is the mechanism
* through which a jiplet routes a request toward its destination.
*
* In some circumstances, the received Request-URI is placed into
* the target set without being modified. For that target, the
* replacement above is effectively a no-op.
*
*/
// All the targets URI are already canonicalized
if (requestURI.isSipURI())
{
clonedRequest.setRequestURI(targetURI);
}
//
// 3. Max-Forwards
//
/*
* If the copy contains a Max-Forwards header field, the jiplet MUST
* decrement its value by one (1). If the copy does not contain a
* Max-Forwards header field, the jiplet MUST add one with a field
* value, which SHOULD be 70.
*/
MaxForwardsHeader mf = (MaxForwardsHeader) clonedRequest
.getHeader(MaxForwardsHeader.NAME);
if (mf == null)
{
mf = headerFactory.createMaxForwardsHeader(70);
clonedRequest.addHeader(mf);
}
else
{
int max = mf.getMaxForwards() - 1;
mf.setMaxForwards(max);
}
//
// 4. Record-Route
//
/*
* The URI placed in the Record-Route header field value MUST be a
* SIP or SIPS URI. This URI MUST contain an lr parameter (see
* Section 19.1.1). This URI MAY be different for each destination
* the request is forwarded to. The URI SHOULD NOT contain the
* transport parameter.
*/
ListeningPoint defaultLP = jiplet.getListeningPointDefault();
SipProvider sipProvider = jiplet.getSipProvider(defaultLP);
if (addRecordRoute)
{
// Only in stateful forwarding
// We add our jiplet RecordRoute header to the top of the
// list - use the following recommended mechanism to handle multi-homing
/*
* If the URI placed in the Record-Route header field needs to
* be rewritten when it passes back through in a response, the
* URI MUST be distinct enough to locate at that time. (The
* request may spiral through this proxy, resulting in more than
* one Record-Route header field value being added). Item 8 of
* Section 16.7 recommends a mechanism to make the URI
* sufficiently distinct.
*
* The proxy MAY include parameters in the Record-Route header
* field value. These will be echoed in some responses to the
* request such as the 200 (OK) responses to INVITE. Such
* parameters may be useful for keeping state in the message
* rather than the proxy.
*/
if (stateful)
{
SipURI sipURI = addressFactory.createSipURI(null, defaultLP
.getIPAddress());
sipURI.setPort(defaultLP.getPort());
sipURI.setTransportParam(defaultLP.getTransport());
sipURI.setLrParam();
// save the IP address and port the request came in on, for
// rewriting the record route header later in the response
// put it in this forwarded message itself - user part of RR
SipProvider sourceProvider = (SipProvider) requestEvent
.getSource();
ListeningPoint sourceLp = sourceProvider
.getListeningPoints()[0];
sipURI.setUser(sourceLp.getIPAddress() + '-' + sourceLp.getPort());
Address address = addressFactory
.createAddress(null, sipURI);
RecordRouteHeader recordRouteHeader = headerFactory
.createRecordRouteHeader(address);
ListIterator recordRouteHeaders = clonedRequest
.getHeaders(RecordRouteHeader.NAME);
clonedRequest.removeHeader(RecordRouteHeader.NAME);
ArrayList v = new ArrayList();
v.add(recordRouteHeader);
// add the other record route headers.
while (recordRouteHeaders != null
&& recordRouteHeaders.hasNext())
{
recordRouteHeader = (RecordRouteHeader) recordRouteHeaders
.next();
v.add(recordRouteHeader);
}
for (int j = 0; j < v.size(); j++)
{
recordRouteHeader = (RecordRouteHeader) v.get(j);
clonedRequest.addHeader(recordRouteHeader);
}
}
}
//
// 5. Add Additional Header Fields
//
// No Additional headers to add...
//
// 6. Postprocess routing information
//
/*
* If the copy contains a Route header field, the jiplet MUST
* inspect the URI in its first value. If that URI does not contain
* an lr parameter, the jiplet MUST modify the copy as follows: -
* The jiplet MUST place the Request-URI into the Route header field
* as the last value. - The jiplet MUST then place the first Route
* header field value into the Request-URI and remove that value
* from the Route header field.
*/
// Strip first route if it is the jiplet UIR adn lr parameter
ListIterator routes = clonedRequest.getHeaders(RouteHeader.NAME);
if (routes != null && routes.hasNext())
{
RouteHeader routeHeader = (RouteHeader) routes.next();
Address routeAddress = routeHeader.getAddress();
URI routeURI = routeAddress.getURI();
if (routeURI.isSipURI() && ((SipURI) routeURI).hasLrParam())
{
String host = ((SipURI) routeURI).getHost();
int port = ((SipURI) routeURI).getPort();
if (jiplet.hasAddress(host, port))
{
routes.remove();
}
}
}
//
// 7. Determine Next-Hop Address, Port, and Transport
//
/*
* the jiplet applies the procedures listed in [4] as follows to
* determine where to send the request. If the jiplet has
* reformatted the request to send to a strict-routing element as
* described in step 6 above, the jiplet MUST apply those procedures
* to the Request-URI of the request. Otherwise, the jiplet MUST
* apply the procedures to the first value in the Route header
* field, if present, else the Request-URI. The procedures will
* produce an ordered set of (address, port, transport) tuples.
* Independently of which URI is being used as input to the
* procedures of [4], if the Request-URI specifies a SIPS resource,
* the jiplet MUST follow the procedures of [4] as if the input URI
* were a SIPS URI.
*/
// Determine Next-Hop Address, Port, and Transport will be done by
// the stack.
//
// 8. Add a Via header field value
//
/*
* The jiplet MUST insert a Via header field value into the copy
* before the existing Via header field values.
*/
ViaHeader viaHeader = null;
String transport = defaultLP.getTransport();
if (clonedRequest.getMethod().equals(Request.CANCEL))
{
// Branch Id will be assigned by the stack.
viaHeader = headerFactory.createViaHeader(defaultLP.getIPAddress(),
defaultLP.getPort(), transport != null ? transport : "", null);
// Cancel is hop by hop so remove all other via headers.
clonedRequest.removeHeader(ViaHeader.NAME);
}
else
{
String branch = ProxyUtilities.generateBranchId();
viaHeader = headerFactory.createViaHeader(defaultLP.getIPAddress(),
defaultLP.getPort(), transport != null ? transport : "", branch);
}
if (viaHeader != null)
clonedRequest.addHeader(viaHeader);
/*
* Proxies choosing to detect loops have an additional constraint in
* the value they use for construction of the branch parameter. A
* jiplet choosing to detect loops SHOULD create a branch parameter
* separable into two parts by the implementation. The first part
* MUST satisfy the constraints of Section 8.1.1.7 as described
* above. The second is used to perform loop detection and
* distinguish loops from spirals.
*/
// Not yet implemented
//
// 9. Add a Content-Length header field if necessary
//
ContentTypeHeader contentTypeHeader = (ContentTypeHeader) clonedRequest
.getHeader(ContentTypeHeader.NAME);
if (contentTypeHeader != null)
{
contentTypeHeader.removeParameter("msgr");
}
// if the proxy has been reset, do not send the to tag
if (proxy.isReset() == true)
{
ToHeader to = (ToHeader) clonedRequest.getHeader(ToHeader.NAME);
to.removeParameter("tag");
}
//
// 10. Forward Request
//
if (stateful)
{
forwardRequestStatefully(sipProvider, clonedRequest, requestEvent.getRequest(),
serverTransaction);
}
else
{
forwardRequestStatelessly(sipProvider, clonedRequest, requestEvent.getRequest(),
serverTransaction);
}
//
// 11. Set timer C
//
// Not Implemented....
}
}
/**
* Forward the request statefully.
*
* @param sipProvider --
* sip provider to forward request.
* @param clonedRequest --
* cloned request to forward.
* @param originalRequest --
* incoming request
* @param serverTransaction --
* server transaction used to fwd the request.
* @throws ParseException
* @throws SipException
* @throws JipletException
*/
private void forwardRequestStatefully(SipProvider sipProvider,
Request clonedRequest, Request originalRequest,
ServerTransaction serverTransaction) throws ParseException,
SipException, JipletException
{
MessageFactory messageFactory = jiplet.getMessageFactory();
/*
* A stateful jiplet MUST create a new client transaction for this
* request as described in Section 17.1 and instructs the transaction to
* send the request using the address, port and transport determined in
* step 7
*/
// SERVER TRANSACTION CHECK
if (serverTransaction == null
&& !clonedRequest.getMethod().equals(Request.MESSAGE))
{
// dont create a server transaction for MESSAGE -
// just forward the request statefully through a new
// client transaction.
return;
}
if (originalRequest.getMethod().equals(Request.CANCEL))
{
// reply to the canceled request and maybe to the received CANCEL
// and also send CANCELs to pending client transactions
JipletDialog jdialog = jiplet.getDialog(serverTransaction
.getDialog(), true);
Transaction firstTransaction = (Transaction) jdialog
.getAttribute("firstTransaction");
if (firstTransaction == null)
{
throw new JipletException(
"ERROR, RequestForwarding Cancel, the first transaction"
+ " for the dialog is null");
}
if (firstTransaction instanceof ClientTransaction)
{
return;
}
ServerTransaction firstServerTransaction = (ServerTransaction) firstTransaction;
try
{
// send 487 Request Terminated reply to canceled request
Response response = messageFactory.createResponse(
Response.REQUEST_TERMINATED, firstServerTransaction
.getRequest());
firstServerTransaction.sendResponse(response);
}
catch (InvalidArgumentException e)
{
// inapplicable - it only happens if the Response was created
// by Dialog.createReliableProvisionalResponse(int) and the
// application calls ServerTransaction.sendResponse() to send it
JipletLogger
.error("Cancel response sending failed - invalid send method used.");
}
// find the response context
TransactionsMapping transactionsMapping = jdialog
.getTransactionsMapping();
Vector clientTransactions = transactionsMapping
.getClientTransactions(firstServerTransaction);
if (clientTransactions == null || clientTransactions.isEmpty())
{
// RFC states to send the CANCEL statelessly in this case
forwardRequestStatelessly(sipProvider, clonedRequest,
originalRequest, serverTransaction);
return;
}
try
{
// send OK to CANCEL
Response response = messageFactory.createResponse(Response.OK,
originalRequest);
serverTransaction.sendResponse(response);
}
catch (InvalidArgumentException e)
{
JipletLogger
.error("Cancel response sending failed - invalid send method used.");
}
for (Enumeration e = clientTransactions.elements(); e
.hasMoreElements();)
{
ClientTransaction ct = (ClientTransaction) e.nextElement();
// check if the client transaction can be canceled.
if (ct.getState().equals(TransactionState.COMPLETED)
|| ct.getState().equals(TransactionState.TERMINATED))
{
continue;
}
try
{
JipletDialog clientJipletDialog = (JipletDialog) ct
.getDialog().getApplicationData();
if (clientJipletDialog != null)
{
ClientTransaction client = clientJipletDialog
.getSipProvider().getNewClientTransaction(
ct.createCancel());
client.sendRequest();
}
else
{
ClientTransaction client = sipProvider
.getNewClientTransaction(ct.createCancel());
client.sendRequest();
}
}
catch (Exception ex)
{
jiplet
.error("Couldn't statefully forward CANCEL : Exception "
+ ex.getClass().getName()
+ " : "
+ ex.getMessage()
+ "\n"
+ JipletLogger.getStackTrace(ex));
}
}
clientTransactions.clear();
return;
}
// FROM TAG UPDATE FOR METHODS DIALOG CREATOR
// DIALOG CHECK
// Note that the jiplet server is actually implemented as a back
// to back User Agent.
Dialog dialog = null;
if (serverTransaction != null)
{
dialog = serverTransaction.getDialog();
}
DialogState dialogState = null;
if (dialog != null)
dialogState = dialog.getState();
if ((dialogState == null) || (proxy.isReset() == true))
{
ClientTransaction clientTransaction = sipProvider
.getNewClientTransaction(clonedRequest);
clientTransaction.sendRequest();
// register for response
jiplet.registerForResponse(clonedRequest, 60000L);
if (dialog != null)
{
JipletDialog jdata = jiplet.getDialog(serverTransaction
.getDialog(), true);
TransactionsMapping transactionsMapping = jdata
.getTransactionsMapping();
transactionsMapping.addMapping(serverTransaction,
clientTransaction);
if (clientTransaction.getDialog() != null)
{
JipletDialog clientDialog = jiplet.getDialog(
clientTransaction.getDialog(), true);
if (clientDialog.getSipProvider() == null)
{
clientDialog.setSipProvider(sipProvider);
}
}
}
return;
}
forwardRequestThroughDialog(sipProvider, serverTransaction,
clonedRequest, dialog);
return;
}
/**
* Forward a request statefully through a dialog.
*
* @throws SipException
* @throws ParseException
*/
private void forwardRequestThroughDialog(SipProvider sipProvider,
ServerTransaction serverTransaction, Request clonedRequest,
Dialog dialog) throws JipletException, SipException, ParseException
{
JipletDialog jdata = jiplet.getDialog(serverTransaction.getDialog(),
true);
TransactionsMapping transactionsMapping = jdata
.getTransactionsMapping();
Dialog peerDialog = transactionsMapping
.getPeerDialog(serverTransaction);
if (peerDialog == null)
{
ClientTransaction ct = sipProvider
.getNewClientTransaction(clonedRequest);
ct.sendRequest();
transactionsMapping.addMapping(serverTransaction, ct);
if (ct.getDialog() != null)
{
JipletDialog clientDialog = jiplet.getDialog(
ct.getDialog(), true);
if (clientDialog.getSipProvider() == null)
{
clientDialog.setSipProvider(sipProvider);
}
}
}
else if (clonedRequest.getMethod().equals(Request.ACK))
{
peerDialog.sendAck(clonedRequest);
}
else
{
Request dialogRequest = peerDialog.createRequest(clonedRequest
.getMethod());
Object content = clonedRequest.getContent();
if (content != null)
{
ContentTypeHeader contentTypeHeader = (ContentTypeHeader) clonedRequest
.getHeader(ContentTypeHeader.NAME);
if (contentTypeHeader != null)
dialogRequest.setContent(content, contentTypeHeader);
}
// Copy all the headers from the original request to the
// dialog created request:
ListIterator l = clonedRequest.getHeaderNames();
while (l.hasNext())
{
String name = (String) l.next();
Header header = dialogRequest.getHeader(name);
if (header == null)
{
ListIterator li = clonedRequest.getHeaders(name);
if (li != null)
{
while (li.hasNext())
{
Header h = (Header) li.next();
dialogRequest.addHeader(h);
}
}
}
else
{
if (header instanceof ViaHeader)
{
ListIterator li = clonedRequest.getHeaders(name);
if (li != null)
{
dialogRequest.removeHeader(name);
Vector v = new Vector();
while (li.hasNext())
{
Header h = (Header) li.next();
v.addElement(h);
}
for (int k = (v.size() - 1); k >= 0; k--)
{
Header h = (Header) v.elementAt(k);
dialogRequest.addHeader(h);
}
}
}
}
}
JipletDialog clientDialog = jiplet.getDialog(peerDialog, false);
ClientTransaction clientTransaction = clientDialog.getSipProvider()
.getNewClientTransaction(dialogRequest);
peerDialog.sendRequest(clientTransaction);
transactionsMapping
.addMapping(serverTransaction, clientTransaction);
// register for response
jiplet.registerForResponse(clonedRequest, 60000L);
}
}
private void forwardRequestStatelessly(SipProvider sipProvider,
Request clonedRequest, Request originalRequest,
ServerTransaction serverTransaction) throws SipException
{
// We forward statelessly:
// It means the Request does not create dialogs...
sipProvider.sendRequest(clonedRequest);
// register for response
jiplet.registerForResponse(clonedRequest, 60000L);
}
/**
* @return Returns the addRecordRoute.
*/
public boolean isAddRecordRoute()
{
return addRecordRoute;
}
/**
* @param addRecordRoute
* The addRecordRoute to set.
*/
public void setAddRecordRoute(boolean addRecordRoute)
{
this.addRecordRoute = addRecordRoute;
}
/**
* @return Returns the jiplet.
*/
public Jiplet getJiplet()
{
return jiplet;
}
/**
* @param jiplet
* The jiplet to set.
*/
public void setJiplet(Jiplet proxy)
{
this.jiplet = proxy;
}
/**
* @return Returns the requestEvent.
*/
public RequestEvent getRequestEvent()
{
return requestEvent;
}
/**
* @param requestEvent
* The requestEvent to set.
*/
public void setRequestEvent(RequestEvent requestEvent)
{
this.requestEvent = requestEvent;
}
/**
* @return Returns the serverTransaction.
*/
public ServerTransaction getServerTransaction()
{
return serverTransaction;
}
/**
* @param serverTransaction
* The serverTransaction to set.
*/
public void setServerTransaction(ServerTransaction serverTransaction)
{
this.serverTransaction = serverTransaction;
}
/**
* @return Returns the stackIPAddress.
*/
public String getStackIPAddress()
{
return jiplet.getListeningPointDefault().getIPAddress();
}
/**
* @return Returns the statefulForwarding.
*/
public boolean isStateful()
{
return stateful;
}
/**
* @param statefulForwarding
* The statefulForwarding to set.
*/
public void setStateful(boolean statefulForwarding)
{
this.stateful = statefulForwarding;
}
}