package com.ericsson.ssa.container;
import java.net.InetSocketAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ListIterator;
import java.util.Set;
import javax.servlet.sip.Address;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.URI;
import org.jvnet.glassfish.comms.util.LogUtil;
import com.ericsson.ssa.config.annotations.Configuration;
import com.ericsson.ssa.container.reporter.Reporter;
import com.ericsson.ssa.sip.AddressImpl;
import com.ericsson.ssa.sip.Header;
import com.ericsson.ssa.sip.Layer;
import com.ericsson.ssa.sip.LayerHelper;
import com.ericsson.ssa.sip.SipFactoryImpl;
import com.ericsson.ssa.sip.SipServletRequestImpl;
import com.ericsson.ssa.sip.SipServletResponseImpl;
import com.ericsson.ssa.sip.SipURIImpl;
import com.ericsson.ssa.sip.ViaImpl;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetTuple;
/**
* Implementation of sip-outbound-15 rfc draft.
* @author Adrian Szwej
*
*/
public class OutboundFlowManager implements Layer {
private static MessageDigest md;
public static final String OUTBOUND_PARAM = "ob";
public static final String GRUU_PARAM = "gr";
public static final String SUPP_REQ_OUTBOUND_VALUE = "outbound";
//can be omited to be anononynoues
public static final String INSTANCE_ID_PARAM = "+sip.instance";
public static final String REG_ID_PARAM = "reg-id";
//flags
public static final boolean OUTBOUND_SUPPORT_ENABLED = true;
private Layer _nextLayer = null;
private SipURIImpl m_URI = null;
private SipURIImpl m_SURI = null;
private boolean defaultTCPTransport = false;
private static volatile OutboundFlowManager ofm = null;
private static volatile boolean init = false;
/** Access to the view containing the connection flows. */
private Set<TargetTuple> _connectionView;
public static OutboundFlowManager getInstance() {
if (! init) {
synchronized (OutboundFlowManager.class) {
if (! init) {
ofm = new OutboundFlowManager();
init = true;
}
}
}
return ofm;
}
/**
* Constructor.
*/
public OutboundFlowManager() {
establishURIs();
SipBindingResolver.instance().registerSipBindingListener(new SipBindingListener() {
public void newSipBindingCtxAvaliable(String context) {
//ignore
}
public void sipBindingCtxUpdated(String context) {
//ignore
}
public void publicSipBindingCtxUpdated() {
establishURIs();
}
public void sipBindingCtxRemoved(String context) {
//ignore
}
});
}
public void next(SipServletRequestImpl req) {
if (OUTBOUND_SUPPORT_ENABLED) {
if (true/*isOutboundSupported(req)*/) { //TODO: required for all?
processOutboundRequest(req);
}
} else {
LayerHelper.next(req, this, _nextLayer);
}
}
private boolean isRegisterOutbound(Header contactHeader) {
try {
if (contactHeader != null) {
Address address = contactHeader.getAddressValue();
if (address != null) {
String instanceId = address.getParameter(INSTANCE_ID_PARAM);
String regId = address.getParameter(REG_ID_PARAM);
if (instanceId != null) {
return true;
} else {
return false;
}
}
}
} catch (ServletParseException e) {
}
return false;
}
/**
* Inspects if we are the first hop node.
* @param req
* @return true if there is only one via present.
*/
private boolean isFirstHop(SipServletRequestImpl req) {
ListIterator<String> viaIterator = req.getHeaders(Header.VIA);
if (viaIterator.hasNext()) {
viaIterator.next(); //just swallow
return !(viaIterator.hasNext());
}
return false;
}
/**
* Checks if outbound is supported. Checks the required and supported header.
* @param message request or response
* @return true if outbound is required and supporte by the UA
*/
private boolean isOutboundSupported(SipServletMessage message) {
ListIterator<String> headerIter = message.getHeaders(Header.REQUIRE);
while (headerIter.hasNext()) {
if (headerIter.next().equals(SUPP_REQ_OUTBOUND_VALUE)) {
return true;
}
}
headerIter = message.getHeaders(Header.SUPPORTED);
while (headerIter.hasNext()) {
if (headerIter.next().equals(SUPP_REQ_OUTBOUND_VALUE)) {
return true;
}
}
return false;
}
private void processOutboundRequest(SipServletRequestImpl req) {
Header contactHeader = req.getRawHeader(Header.CONTACT);
if ("REGISTER".equals(req.getMethod())) {
if (isFirstHop(req)) {
if (isRegisterOutbound(contactHeader) && isOutboundSupported(req)) {
// add path header as RFC3327 so we are included in
// subsequent request to the UAC
addPathHeader(req);
}
}
LayerHelper.next(req, this, _nextLayer);
} else {
Header routeHeader = req.getRawHeader(Header.ROUTE);
if (isOutboundForwardIndicated(routeHeader)) {
forwardRequest(req);
} else {
LayerHelper.next(req, this, _nextLayer);
//last possible place to find the outbound indicator
/* if (isOutboundIndicated(req.getRequestURI())) {
//if its in request URI, then route header must exist otherwise we can't forward to flow
if (routeHeader != null)
forwardRequest(req);
else {
try {
req.createResponse(430).send();
} catch (Exception e) {
e.printStackTrace();
}
}
}
*/
}
}
}
/**
* Forwards the request to specified connection flow
* @param req
* @throws Exception
*/
private void forward(SipServletRequestImpl req) throws Exception {
//decrease max forward
int maxForwards = req.getMaxForwards();
req.setMaxForwards(--maxForwards);
pushVia(req);
// Alter connection and dispatch
LayerHelper.resetDispatcher(req, NetworkManager.getInstance());
req.popDispatcher().dispatch(req);
}
public void next(SipServletResponseImpl resp) {
if (OUTBOUND_SUPPORT_ENABLED) {
if (resp.getMethod().equals("REGISTER") && resp.getStatus() == 200) {
//provide
if (isOutboundSupported(resp)) {
String flowTimer = resp.getHeader(Header.FLOW_TIMER);
if (flowTimer != null) {
//connectionManager.provideKeepAlive(resp.getRemote(), Integer.parseInt(flowTimer));
} else {
//connectionManager.provideKeepAlive(resp.getRemote());
}
}
LayerHelper.next(resp, this, _nextLayer);
} else {
// Get topmost header
Header viaHeader = resp.getRawHeader(Header.VIA);
if (viaHeader != null) {
ListIterator<String> viaIterator;
viaIterator = viaHeader.getValues();
if (viaIterator.hasNext()) {
ViaImpl via = new ViaImpl(viaIterator.next());
String outboundIndicated = via.getParameter(OUTBOUND_PARAM);
if (outboundIndicated != null) {
// This response was received from the back-end,
// pop Via, extract connection and forward it to the client.
viaHeader.setReadOnly(false);
viaIterator.remove();
viaHeader.setReadOnly(true);
// Alter connection and dispatch
LayerHelper.resetDispatcher(resp, NetworkManager.getInstance());
resp.popDispatcher().dispatch(resp); //will be resolved by via
}
LayerHelper.next(resp, this, _nextLayer);
}
}
// No Via? The response must be corrupt, drop it!
//throw new SipRoutingException(
// "No Via on response, shall never happen; drop the response!");
}
} else {
LayerHelper.next(resp, this, _nextLayer);
}
}
private void pushVia(SipServletRequestImpl request) {
Header viaHeader = request.getRawHeader(Header.VIA);
ViaImpl topVia = null;
String branchId;
if (viaHeader == null) {
} else {
topVia = new ViaImpl(viaHeader.getValue());
branchId = createBranchId(request, topVia);
ViaImpl via = new ViaImpl("SIP", SipTransports.TCP_PROT.name(), getContainerAddress().toString());
via.setParameter(OUTBOUND_PARAM, "");
via.setParameter(ViaImpl.PARAM_BRANCH, branchId);
viaHeader.setValue(via.toString(), true);
}
}
/**
* Checks if the request is oncoming or outgoing
* @param recordRoute
* @return
*/
private boolean isRequestIncoming(Header routeHeader, TargetTuple remotePoint) {
FlowToken flowToken = extractFlowToken(routeHeader);
try {
flowToken.validate();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
private boolean isOutboundIndicated(Header contactHeader) {
try {
URI uri = contactHeader.getAddressValue().getURI();
return (isOutboundIndicated(uri, true) || false/*TODO: is GRUU*/);
} catch (ServletParseException e) {
}
return false;
}
/**
* Checks if there is any encoded connection information in the header indicated by the ob parameter
* @param routeHeader
* @return
*/
private boolean isOutboundForwardIndicated(Header routeHeader) {
try {
return (routeHeader != null && isOutboundIndicated(routeHeader.getAddressValue().getURI(), false));
} catch (ServletParseException e) {
}
return false;
}
/**
* Checks if outbound indicator exists in the URI.
* @param uri the uri to check for the outbound parameter
* @return true if there is an outbound parameter
*/
private boolean isOutboundIndicated(URI uri, boolean gruuCheck) {
if (uri.getParameter(OUTBOUND_PARAM) == null) {
if (gruuCheck)
return uri.getParameter(GRUU_PARAM) != null;
} else {
return true;
}
return false;
}
/**
* Gets the address of this container and including the flowtoken as user part.
* @param flowToken token to use for user prt-
* @return address of this container
*/
private SipURI getContainerAddress(FlowToken flowToken) {
SipURIImpl sipURI = getContainerAddress();
sipURI.setUser(flowToken.getEncodedFlowId());
sipURI.setLrParam(true);
return sipURI;
}
/**
* Gets the address of this container.
* @return address of this container
*/
private SipURIImpl getContainerAddress() {
SipURIImpl sipURI = getVipSipUri();
return sipURI;
}
/**
* Ensures that the node stays in the path for incoming new call.
*/
private void addPathHeader(SipServletRequestImpl req) {
FlowToken flowToken = createFlowToken(req.getInitialRemote(), req.getLocal());
SipURI sipURI = getContainerAddress(flowToken);
sipURI.setParameter("ob", "");
Header header = Header.createFormatted(Header.PATH, req);
header.setValue("<" + sipURI.toString() + ">", false);
req.addHeader(header);
}
private FlowToken extractFlowToken(Header routeHeader) {
FlowToken flowToken = null;
if (routeHeader != null) {
URI sipURI = null;
try {
sipURI = routeHeader.getAddressValue().getURI();
if (sipURI instanceof SipURIImpl) {
String flowId = ((SipURIImpl)sipURI).getUser();
flowToken = new FlowToken(flowId);
}
} catch (ServletParseException e) {
}
}
return flowToken;
}
/**
* Forwards the request stateless.
* @param req, the request to forward stateless
*/
private void forwardRequest(SipServletRequestImpl req)
{
//find the flow to forward the request
Header routeHeader = req.getRawHeader(Header.ROUTE);
FlowToken flowToken = extractFlowToken(routeHeader);
if (flowToken != null) {
try {
flowToken.validate();
TargetTuple flowTuple = flowToken.getRemote();
//connectionView.getInstance().getConnectionView()
if (getConnectioView().contains(flowTuple)) {
//remove the route header
Address poppedRoute = req.popRouteHeader();
//remove if exists, exists for incoming dialog creational requests
poppedRoute.getURI().removeParameter(OUTBOUND_PARAM);
//we must check if there is already a value in route. eg put by clb.
if (SipFactoryImpl.isDialogCreational(req.getMethod())) {
//make sure we stay in the path
updateRecordRouteHeader(req, poppedRoute);
}
req.setRemote(flowTuple);
forward(req);
} else {
//flow does not exist, don't even try to reestablish
sendError(req, 430);
}
} catch (Exception e) {
sendError(req, 403);
}
}
}
/**
* Updates the Record-Route if exists, otherwise creates a new one.
* @param req
* @return
*/
private void updateRecordRouteHeader(SipServletRequestImpl req, Address poppedRoute) {
Header recordRoute = req.getRawHeader(Header.RECORD_ROUTE);
if (recordRoute == null) {
recordRoute = Header.createFormatted(Header.RECORD_ROUTE, req);
req.addHeader(recordRoute);
}
recordRoute.setValue('<' + poppedRoute.getURI().toString() + '>', true);
}
private static void sendError(SipServletRequestImpl req, int errorCode) {
try {
//outbound indicated, but flowtoken has been tampered
req.createResponse(errorCode).send();
} catch (Exception e1) {
LogUtil.SIP_LOGGER.getLogger().warning("Cannot send response with errorcode: " + errorCode);
}
}
/**
* Creates a new flowtoken based on the connection information.
*
* @return FlowToken object identifing the flow.
*/
private static FlowToken createFlowToken(TargetTuple remotePoint, InetSocketAddress localPoint) {
FlowToken flowToken = new FlowToken(remotePoint, localPoint);
return flowToken;
}
public void addFlowOnRecordRoute(SipServletRequestImpl req) {
Header contactHeader = req.getRawHeader(Header.CONTACT);
if (contactHeader != null && isOutboundIndicated(contactHeader)) {
if (SipFactoryImpl.isDialogCreational(req.getMethod()) && isFirstHop(req)) {
addRecordRouteHeader(req);
}
}
}
/**
* Ensures that the node stays in the path for the call.
*/
private void addRecordRouteHeader(SipServletRequestImpl req) {
FlowToken flowToken = createFlowToken(req.getInitialRemote(), req.getLocal());
SipURI sipURI = getContainerAddress(flowToken);
Header header = req.getRawHeader(Header.RECORD_ROUTE);
if (header == null) {
header = Header.createFormatted(Header.RECORD_ROUTE, req);
//add route so that any request passes this node
req.addHeader(header);
}
header.setValue("<" + sipURI.toString() + ">", false);
}
/**
* Gets the connection view once from the GrizzltNetworkManager
* @return connection view.
*/
private Set<TargetTuple> getConnectioView() {
if (_connectionView == null) {
synchronized (this) {
if (_connectionView == null)
_connectionView = GrizzlyNetworkManager.getInstance().getConnectionView();
}
}
return _connectionView;
}
public SipURIImpl getVipSipUri() {
return (SipURIImpl) m_URI.clone();
}
public SipURIImpl getVipSipsUri() {
return (SipURIImpl) m_SURI.clone();
}
/**
* Sets the value for this parameter, this is done via reflection when
* starting Layer is started.
*
* @param defaultTCPTransport
* true if TCP should be used false if UDP should be used.
*/
@Configuration(key = "DefaultTcpTransport", node = "/SipService/SipProtocol")
public void setDefaultTCPTransport(Boolean aDefaultTCPTransport) {
defaultTCPTransport = aDefaultTCPTransport;
establishURIs();
}
private void establishURIs() {
m_URI = null;
m_SURI = null;
SipURIImpl[] ifs = SipBindingResolver.getInterfaces();
//Find first UDP || TCP ||TLS
for (int i = 0; i < ifs.length; i++) {
if ((m_SURI == null) && ifs[i].isSecure()) {
m_SURI = ifs[i];
} else if ((m_URI == null) && defaultTCPTransport && ifs[i].getTransportParam().equalsIgnoreCase("TCP")) {
m_URI = ifs[i];
} else if ((m_URI == null) && !defaultTCPTransport && ifs[i].getTransportParam().equalsIgnoreCase("UDP")) {
m_URI = ifs[i].clone();
m_URI.removeParameter("transport");
}
}
}
/**
* Create a branch ID
* @param req the request
* @param via the via to use for creating the branchId
* @return the brancId
*/
public static String createBranchId(SipServletRequestImpl req, ViaImpl via) {
MessageDigest messageDigest = getMessageDigest();
try {
StringBuilder sb = new StringBuilder();
sb.append(req.getRequestURI().toString());
sb.append(req.getFromImpl().getParameter(AddressImpl.TAG_PARAM));
sb.append(req.getCallId());
sb.append(req.getCSeqNumber());
sb.append(via.toString());
MessageDigest localMD = (MessageDigest) messageDigest.clone();
byte[] hash = localMD.digest(sb.toString().getBytes());
sb = new StringBuilder();
for (int i = 0; i < hash.length; i++) {
String d = Integer.toHexString(new Byte(hash[i]).intValue() & 0xFF);
if (d.length() == 1) {
sb.append('0');
}
sb.append(d);
}
return sb.toString();
} catch (CloneNotSupportedException ex) {
}
return null;
}
private static MessageDigest getMessageDigest() throws Error {
if (md == null) {
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
// Should never happen!
throw new Error(e);
}
}
return md;
}
public void registerNext(Layer layer) {
_nextLayer = layer;
}
public void setReporters(String reporters) {
}
public void dispatch(SipServletRequestImpl req) {
req.popDispatcher().dispatch(req);
}
public void dispatch(SipServletResponseImpl resp) {
resp.popDispatcher().dispatch(resp);
}
public Reporter getReporter() {
return null;
}
}