/* * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.mobicents.servlet.sip.message; import gov.nist.javax.sip.header.ims.PathHeader; import gov.nist.javax.sip.message.SIPMessage; import java.io.Serializable; import java.text.ParseException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.ServletException; import javax.servlet.sip.B2buaHelper; import javax.servlet.sip.SipServletMessage; import javax.servlet.sip.SipServletRequest; import javax.servlet.sip.SipServletResponse; import javax.servlet.sip.SipSession; import javax.servlet.sip.TooManyHopsException; import javax.servlet.sip.UAMode; import javax.servlet.sip.SipSession.State; import javax.servlet.sip.ar.SipApplicationRoutingDirective; import javax.sip.ClientTransaction; import javax.sip.ServerTransaction; import javax.sip.Transaction; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.header.ContactHeader; import javax.sip.header.ContentDispositionHeader; import javax.sip.header.ContentLengthHeader; import javax.sip.header.ContentTypeHeader; import javax.sip.header.FromHeader; import javax.sip.header.Header; 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.Request; import org.apache.log4j.Logger; import org.mobicents.servlet.sip.JainSipUtils; import org.mobicents.servlet.sip.SipFactories; import org.mobicents.servlet.sip.core.ApplicationRoutingHeaderComposer; import org.mobicents.servlet.sip.core.ExtendedListeningPoint; import org.mobicents.servlet.sip.core.RoutingState; import org.mobicents.servlet.sip.core.session.MobicentsSipApplicationSession; import org.mobicents.servlet.sip.core.session.MobicentsSipSession; import org.mobicents.servlet.sip.core.session.SessionManagerUtil; import org.mobicents.servlet.sip.core.session.SipManager; import org.mobicents.servlet.sip.core.session.SipSessionKey; /** * Implementation of the B2BUA helper class. * * * @author mranga * @author Jean Deruelle */ public class B2buaHelperImpl implements B2buaHelper, Serializable { private static final long serialVersionUID = 1L; private static transient Logger logger = Logger.getLogger(B2buaHelperImpl.class); protected transient static final HashSet<String> singletonHeadersNames = new HashSet<String>(); static { singletonHeadersNames.add(FromHeader.NAME); singletonHeadersNames.add(ToHeader.NAME); singletonHeadersNames.add(CSeqHeader.NAME); singletonHeadersNames.add(CallIdHeader.NAME); singletonHeadersNames.add(MaxForwardsHeader.NAME); singletonHeadersNames.add(ContentLengthHeader.NAME); singletonHeadersNames.add(ContentDispositionHeader.NAME); singletonHeadersNames.add(ContentTypeHeader.NAME); //TODO are there any other singleton headers ? } protected transient static final HashSet<String> b2buaSystemHeaders = new HashSet<String>(); static { b2buaSystemHeaders.add(CallIdHeader.NAME); b2buaSystemHeaders.add(CSeqHeader.NAME); b2buaSystemHeaders.add(ViaHeader.NAME); b2buaSystemHeaders.add(RouteHeader.NAME); b2buaSystemHeaders.add(RecordRouteHeader.NAME); b2buaSystemHeaders.add(PathHeader.NAME); } //Map to handle linked sessions private Map<SipSessionKey, SipSessionKey> sessionMap = null; //Map to handle responses to original request and cancel on original request private transient Map<SipSessionKey, SipServletRequest> originalRequestMap = null; private transient SipFactoryImpl sipFactoryImpl; private transient SipManager sipManager; public B2buaHelperImpl() { sessionMap = new ConcurrentHashMap<SipSessionKey, SipSessionKey>(); originalRequestMap = new ConcurrentHashMap<SipSessionKey, SipServletRequest>(); } /* * (non-Javadoc) * @see javax.servlet.sip.B2buaHelper#createRequest(javax.servlet.sip.SipServletRequest, boolean, java.util.Map) */ public SipServletRequest createRequest(SipServletRequest origRequest, boolean linked, Map<String, List<String>> headerMap) throws TooManyHopsException { if(origRequest == null) { throw new NullPointerException("original request cannot be null"); } if(origRequest.getMaxForwards() == 0) { throw new TooManyHopsException(); } try { final SipServletRequestImpl origRequestImpl = (SipServletRequestImpl) origRequest; Request newRequest = (Request) origRequestImpl.message.clone(); ((SIPMessage)newRequest).setApplicationData(null); //content should be copied too, so commented out // newRequest.removeContent(); //removing the via header from original request newRequest.removeHeader(ViaHeader.NAME); // Remove the route header ( will point to us ). // commented as per issue 649 // newRequest.removeHeader(RouteHeader.NAME); // String tag = Integer.toString((int) (Math.random()*1000)); // ((FromHeader) newRequest.getHeader(FromHeader.NAME)).setParameter("tag", tag); // Remove the record route headers. This is a new call leg. newRequest.removeHeader(RecordRouteHeader.NAME); //For non-REGISTER requests, the Contact header field is not copied //but is populated by the container as usualB2buaHelperImpl if(!Request.REGISTER.equalsIgnoreCase(origRequest.getMethod())) { newRequest.removeHeader(ContactHeader.NAME); } //Creating new call id final ExtendedListeningPoint extendedListeningPoint = sipFactoryImpl.getSipNetworkInterfaceManager().getExtendedListeningPoints().next(); final CallIdHeader callIdHeader = SipFactories.headerFactory.createCallIdHeader(extendedListeningPoint.getSipProvider().getNewCallId().getCallId()); newRequest.setHeader(callIdHeader); final List<String> contactHeaderSet = retrieveContactHeaders(headerMap, newRequest); final FromHeader newFromHeader = (FromHeader) newRequest.getHeader(FromHeader.NAME); newFromHeader.removeParameter("tag"); ((ToHeader) newRequest.getHeader(ToHeader.NAME)) .removeParameter("tag"); final MobicentsSipSession originalSession = origRequestImpl.getSipSession(); final MobicentsSipApplicationSession appSession = originalSession .getSipApplicationSession(); newFromHeader.setTag(ApplicationRoutingHeaderComposer.getHash(sipFactoryImpl.getSipApplicationDispatcher(), originalSession.getKey().getApplicationName(), appSession.getKey().getId())); final SipSessionKey key = SessionManagerUtil.getSipSessionKey(appSession.getKey().getId(), originalSession.getKey().getApplicationName(), newRequest, false); final MobicentsSipSession session = appSession.getSipContext().getSipManager().getSipSession(key, true, sipFactoryImpl, appSession); session.setHandler(originalSession.getHandler()); final SipServletRequestImpl newSipServletRequest = new SipServletRequestImpl( newRequest, sipFactoryImpl, session, null, null, JainSipUtils.dialogCreatingMethods.contains(newRequest.getMethod())); //JSR 289 Section 15.1.6 newSipServletRequest.setRoutingDirective(SipApplicationRoutingDirective.CONTINUE, origRequest); //If Contact header is present in the headerMap //then relevant portions of Contact header is to be used in the request created, //in accordance with section 4.1.3 of the specification. for (String contactHeaderValue : contactHeaderSet) { newSipServletRequest.addHeaderInternal(ContactHeader.NAME, contactHeaderValue, true); } originalRequestMap.put(originalSession.getKey(), origRequest); originalRequestMap.put(session.getKey(), newSipServletRequest); if (linked) { sessionMap.put(originalSession.getKey(), session.getKey()); sessionMap.put(session.getKey(), originalSession.getKey()); dumpLinkedSessions(); } session.setB2buaHelper(this); originalSession.setB2buaHelper(this); return newSipServletRequest; } catch (ParseException ex) { logger.error("Unexpected parse exception ", ex); throw new IllegalArgumentException( "Illegal arg encountered while creating b2bua", ex); } catch (ServletException ex) { logger.error("Unexpected exception ", ex); throw new IllegalArgumentException( "Unexpected problem while creating b2bua", ex); } } /* * (non-Javadoc) * @see javax.servlet.sip.B2buaHelper#createRequest(javax.servlet.sip.SipSession, javax.servlet.sip.SipServletRequest, java.util.Map) */ public SipServletRequest createRequest(SipSession session, SipServletRequest origRequest, Map<String, List<String>> headerMap) { if(origRequest == null) { throw new NullPointerException("original request cannot be null"); } try { final SipServletRequestImpl origRequestImpl = (SipServletRequestImpl) origRequest; final MobicentsSipSession originalSession = origRequestImpl.getSipSession(); final MobicentsSipSession sessionImpl = (MobicentsSipSession) session; final SipServletRequestImpl newSubsequentServletRequest = (SipServletRequestImpl) session.createRequest(origRequest.getMethod()); //For non-REGISTER requests, the Contact header field is not copied //but is populated by the container as usual //commented since this is not true in this case since this is a subsequent request // this is needed for sending challenge requests // if(!Request.REGISTER.equalsIgnoreCase(origRequest.getMethod())) { // newSubsequentServletRequest.getMessage().removeHeader(ContactHeader.NAME); // } //If Contact header is present in the headerMap //then relevant portions of Contact header is to be used in the request created, //in accordance with section 4.1.3 of the specification. //They will be added later after the sip servlet request has been created final List<String> contactHeaderSet = retrieveContactHeaders(headerMap, (Request) newSubsequentServletRequest.getMessage()); //If Contact header is present in the headerMap //then relevant portions of Contact header is to be used in the request created, //in accordance with section 4.1.3 of the specification. for (String contactHeaderValue : contactHeaderSet) { newSubsequentServletRequest.addHeaderInternal(ContactHeader.NAME, contactHeaderValue, true); } //Fix for Issue 585 by alexandre sova if(origRequest.getContent() != null && origRequest.getContentType() != null) { newSubsequentServletRequest.setContentLength(origRequest.getContentLength()); newSubsequentServletRequest.setContent(origRequest.getContent(), origRequest.getContentType()); } if(logger.isDebugEnabled()) { logger.debug("newSubsequentServletRequest = " + newSubsequentServletRequest); } originalRequestMap.put(originalSession.getKey(), origRequest); originalRequestMap.put(((MobicentsSipSession)session).getKey(), newSubsequentServletRequest); sessionMap.put(originalSession.getKey(), sessionImpl.getKey()); sessionMap.put(sessionImpl.getKey(), originalSession.getKey()); dumpLinkedSessions(); sessionImpl.setB2buaHelper(this); originalSession.setB2buaHelper(this); return newSubsequentServletRequest; } catch (Exception ex) { logger.error("Unexpected exception ", ex); throw new IllegalArgumentException( "Illegal arg ecnountered while creatigng b2bua", ex); } } /** * @param headerMap * @param newRequest * @return * @throws ParseException */ private final static List<String> retrieveContactHeaders( Map<String, List<String>> headerMap, Request newRequest) throws ParseException { List<String> contactHeaderList = new ArrayList<String>(); if(headerMap != null) { for (Entry<String, List<String>> entry : headerMap.entrySet()) { final String headerName = entry.getKey(); if(!headerName.equalsIgnoreCase(ContactHeader.NAME)) { if(b2buaSystemHeaders.contains(headerName)) { throw new IllegalArgumentException(headerName + " in the provided map is a system header"); } for (String value : entry.getValue()) { final Header header = SipFactories.headerFactory.createHeader( headerName, value); if(! singletonHeadersNames.contains(header.getName())) { newRequest.addHeader(header); } else { newRequest.setHeader(header); } } } else { contactHeaderList = headerMap.get(headerName); } } } return contactHeaderList; } /* * (non-Javadoc) * @see javax.servlet.sip.B2buaHelper#createResponseToOriginalRequest(javax.servlet.sip.SipSession, int, java.lang.String) */ public SipServletResponse createResponseToOriginalRequest( SipSession session, int status, String reasonPhrase) { if (session == null) { throw new NullPointerException("Null arg"); } if(!session.isValid()) { throw new IllegalArgumentException("session is invalid !"); } final MobicentsSipSession sipSession = (MobicentsSipSession) session; final Transaction trans = sipSession.getSessionCreatingTransaction(); final TransactionApplicationData appData = (TransactionApplicationData) trans.getApplicationData(); final SipServletRequestImpl sipServletRequestImpl = (SipServletRequestImpl) appData.getSipServletMessage(); if(RoutingState.FINAL_RESPONSE_SENT.equals(sipServletRequestImpl.getRoutingState())) { throw new IllegalStateException("subsequent response is inconsistent with an already sent response. a Final response has already been sent ! "); } return sipServletRequestImpl.createResponse(status, reasonPhrase); } /* * (non-Javadoc) * @see javax.servlet.sip.B2buaHelper#getLinkedSession(javax.servlet.sip.SipSession) */ public SipSession getLinkedSession(final SipSession session) { if ( session == null) { throw new NullPointerException("the argument is null"); } if(!session.isValid()) { throw new IllegalArgumentException("the session is invalid"); } final MobicentsSipSession mobicentsSipSession = (MobicentsSipSession)session; final SipSessionKey sipSessionKey = this.sessionMap.get(mobicentsSipSession.getKey()); if(sipSessionKey == null) { dumpLinkedSessions(); return null; } final MobicentsSipSession linkedSession = sipManager.getSipSession(sipSessionKey, false, null, mobicentsSipSession.getSipApplicationSession()); if(logger.isDebugEnabled()) { if(linkedSession != null) { logger.debug("Linked Session found : " + linkedSession.getKey() + " for this session " + session.getId()); } else { logger.debug("No Linked Session found for this session " + session.getId()); } } if(linkedSession != null) { return linkedSession.getSession(); } else { return null; } } /* * (non-Javadoc) * @see javax.servlet.sip.B2buaHelper#getLinkedSipServletRequest(javax.servlet.sip.SipServletRequest) */ public SipServletRequest getLinkedSipServletRequest(SipServletRequest req) { if ( req == null) { throw new NullPointerException("the argument is null"); } final MobicentsSipSession mobicentsSipSession = (MobicentsSipSession)req.getSession(); final SipSessionKey sipSessionKey = this.sessionMap.get(mobicentsSipSession.getKey()); if(sipSessionKey == null) { return null; } final MobicentsSipSession linkedSipSession = sipManager.getSipSession(sipSessionKey, false, null, mobicentsSipSession.getSipApplicationSession()); if(linkedSipSession == null) { return null; } final SipServletRequest linkedSipServletRequest = originalRequestMap.get(linkedSipSession.getKey()); return linkedSipServletRequest; } /* * (non-Javadoc) * @see javax.servlet.sip.B2buaHelper#getPendingMessages(javax.servlet.sip.SipSession, javax.servlet.sip.UAMode) */ public List<SipServletMessage> getPendingMessages(SipSession session, UAMode mode) { if(!session.isValid()) { throw new IllegalArgumentException("the session is invalid!"); } final MobicentsSipSession sipSessionImpl = (MobicentsSipSession) session; final List<SipServletMessage> retval = new ArrayList<SipServletMessage> (); if (mode.equals(UAMode.UAC)) { final Set<Transaction> ongoingTransactions = sipSessionImpl.getOngoingTransactions(); if(ongoingTransactions != null) { for ( Transaction transaction: ongoingTransactions) { if ( transaction instanceof ClientTransaction) { final TransactionApplicationData tad = (TransactionApplicationData) transaction.getApplicationData(); final SipServletMessage sipServletMessage = tad.getSipServletMessage(); //not specified if ACK is a committed message in the spec but it seems not since Proxy api test //testCanacel101 method adds a header to the ACK and it cannot be on a committed message //so we don't want to return ACK as pending messages here. related to TCK test B2BUAHelper.testCreateRequest002 if (!sipServletMessage.isCommitted() && !Request.ACK.equals(sipServletMessage.getMethod()) && !Request.PRACK.equals(sipServletMessage.getMethod())) { retval.add(sipServletMessage); } for(SipServletResponseImpl sipServletResponseImpl : tad.getSipServletResponses()) { if (!sipServletResponseImpl.isCommitted()) { retval.add(sipServletResponseImpl); } } } } } } else { for ( Transaction transaction: sipSessionImpl.getOngoingTransactions()) { if ( transaction instanceof ServerTransaction) { final TransactionApplicationData tad = (TransactionApplicationData) transaction.getApplicationData(); final SipServletMessage sipServletMessage = tad.getSipServletMessage(); //not specified if ACK is a committed message in the spec but it seems not since Proxy api test //testCanacel101 method adds a header to the ACK and it cannot be on a committed message //so we don't want to return ACK as pending messages here. related to TCK test B2BUAHelper.testCreateRequest002 if (!sipServletMessage.isCommitted() && !Request.ACK.equals(sipServletMessage.getMethod())) { retval.add(sipServletMessage); } } } } return retval; } /* * (non-Javadoc) * @see javax.servlet.sip.B2buaHelper#linkSipSessions(javax.servlet.sip.SipSession, javax.servlet.sip.SipSession) */ public void linkSipSessions(SipSession session1, SipSession session2) { if ( session1 == null) { throw new NullPointerException("First argument is null"); } if ( session2 == null) { throw new NullPointerException("Second argument is null"); } if(!session1.isValid() || !session2.isValid() || State.TERMINATED.equals(((MobicentsSipSession)session1).getState()) || State.TERMINATED.equals(((MobicentsSipSession)session2).getState()) || !session1.getApplicationSession().equals(session2.getApplicationSession()) || sessionMap.get(((MobicentsSipSession)session1).getKey()) != null || sessionMap.get(((MobicentsSipSession)session2).getKey()) != null) { throw new IllegalArgumentException("either of the specified sessions has been terminated " + "or the sessions do not belong to the same application session or " + "one or both the sessions are already linked with some other session(s)"); } this.sessionMap.put(((MobicentsSipSession)session1).getKey(), ((MobicentsSipSession)session2).getKey()); this.sessionMap.put(((MobicentsSipSession)session2).getKey(), ((MobicentsSipSession) session1).getKey()); dumpLinkedSessions(); } /* * (non-Javadoc) * @see javax.servlet.sip.B2buaHelper#unlinkSipSessions(javax.servlet.sip.SipSession) */ public void unlinkSipSessions(SipSession session) { unlinkSipSessionsInternal(session, true); } /* * (non-Javadoc) * @see javax.servlet.sip.B2buaHelper#unlinkSipSessions(javax.servlet.sip.SipSession) */ public void unlinkSipSessionsInternal(SipSession session, boolean checkSession) { if ( session == null) { throw new NullPointerException("the argument is null"); } final MobicentsSipSession key = (MobicentsSipSession) session; if(checkSession) { if(!session.isValid() || State.TERMINATED.equals(key.getState()) || sessionMap.get(key.getKey()) == null) { throw new IllegalArgumentException("the session is not currently linked to another session or it has been terminated"); } } final SipSessionKey value = this.sessionMap.get(key.getKey()); if (value != null) { this.sessionMap.remove(value); } this.sessionMap.remove(key.getKey()); dumpLinkedSessions(); } /** * {@inheritDoc} */ public SipServletRequest createRequest(SipServletRequest origRequest) { try { final SipServletRequestImpl origRequestImpl = (SipServletRequestImpl) origRequest; final Request newRequest = (Request) origRequestImpl.message.clone(); ((SIPMessage)newRequest).setApplicationData(null); //removing the via header from original request newRequest.removeHeader(ViaHeader.NAME); final FromHeader newFromHeader = (FromHeader) newRequest.getHeader(FromHeader.NAME); //assign a new from tag newFromHeader.removeParameter("tag"); //remove the to tag ((ToHeader) newRequest.getHeader(ToHeader.NAME)) .removeParameter("tag"); // Remove the route header ( will point to us ). // commented as per issue 649 // newRequest.removeHeader(RouteHeader.NAME); // Remove the record route headers. This is a new call leg. newRequest.removeHeader(RecordRouteHeader.NAME); //For non-REGISTER requests, the Contact header field is not copied //but is populated by the container as usual if(!Request.REGISTER.equalsIgnoreCase(origRequest.getMethod())) { newRequest.removeHeader(ContactHeader.NAME); } //Creating new call id final ExtendedListeningPoint extendedListeningPoint = sipFactoryImpl.getSipNetworkInterfaceManager().getExtendedListeningPoints().next(); final CallIdHeader callIdHeader = SipFactories.headerFactory.createCallIdHeader(extendedListeningPoint.getSipProvider().getNewCallId().getCallId()); newRequest.setHeader(callIdHeader); final MobicentsSipSession originalSession = origRequestImpl.getSipSession(); final MobicentsSipApplicationSession originalAppSession = originalSession .getSipApplicationSession(); newFromHeader.setTag(ApplicationRoutingHeaderComposer.getHash(sipFactoryImpl.getSipApplicationDispatcher(), originalSession.getKey().getApplicationName(), originalAppSession.getKey().getId())); final SipSessionKey key = SessionManagerUtil.getSipSessionKey(originalAppSession.getKey().getId(), originalSession.getKey().getApplicationName(), newRequest, false); final MobicentsSipSession session = ((SipManager)originalAppSession.getSipContext().getManager()).getSipSession(key, true, sipFactoryImpl, originalAppSession); session.setHandler(originalSession.getHandler()); final SipServletRequestImpl newSipServletRequest = new SipServletRequestImpl( newRequest, sipFactoryImpl, session, null, null, JainSipUtils.dialogCreatingMethods.contains(newRequest.getMethod())); //JSR 289 Section 15.1.6 newSipServletRequest.setRoutingDirective(SipApplicationRoutingDirective.CONTINUE, origRequest); sessionMap.put(originalSession.getKey(), session.getKey()); sessionMap.put(session.getKey(), originalSession.getKey()); dumpLinkedSessions(); originalRequestMap.put(originalSession.getKey(), origRequest); originalRequestMap.put(session.getKey(), newSipServletRequest); session.setB2buaHelper(this); originalSession.setB2buaHelper(this); return newSipServletRequest; } catch (Exception ex) { logger.error("Unexpected exception ", ex); throw new IllegalArgumentException( "Illegal arg ecnountered while creatigng b2bua", ex); } } /** * {@inheritDoc} */ public SipServletRequest createCancel(SipSession session) { final SipServletRequest sipServletRequest = originalRequestMap.get(((MobicentsSipSession)session).getKey()); final SipServletRequestImpl sipServletRequestImpl = (SipServletRequestImpl)sipServletRequest.createCancel(); ((MobicentsSipSession)sipServletRequestImpl.getSession()).setB2buaHelper(this); return sipServletRequestImpl; } /** * @return the sipFactoryImpl */ public SipFactoryImpl getSipFactoryImpl() { return sipFactoryImpl; } /** * @param sipFactoryImpl the sipFactoryImpl to set */ public void setSipFactoryImpl(SipFactoryImpl sipFactoryImpl) { this.sipFactoryImpl = sipFactoryImpl; } /** * @return the sipManager */ public SipManager getSipManager() { return sipManager; } /** * @param sipManager the sipManager to set */ public void setSipManager(SipManager sipManager) { this.sipManager = sipManager; } private void dumpLinkedSessions() { if(logger.isDebugEnabled()) { for (SipSessionKey key : sessionMap.keySet()) { logger.debug(key + " tied to session " + sessionMap.get(key)); } } } }