/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.sip;
import com.ericsson.ssa.config.LayerHandler;
import com.ericsson.ssa.container.SipBindingResolver;
import org.jvnet.glassfish.comms.deployment.backend.SessionCase;
import org.jvnet.glassfish.comms.security.auth.impl.AuthInfoImpl;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
// inserted by hockey (automatic)
import org.jvnet.glassfish.comms.util.LogUtil;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.sip.Address;
import javax.servlet.sip.AuthInfo;
import javax.servlet.sip.Parameterable;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.ar.SipApplicationRoutingDirective;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.URI;
/**
* @author ekrigro
* @reviewed ejoelbi 2006-oct-19
*/
public class SipFactoryImpl implements SipFactory {
public static final String SIP_CHARSET = "UTF-8";
public static final String PROTOCOL_LINE = "SIP/2.0";
public static final String NEW_LINE = "\r\n";
public static final String SIP_URI_PROTOCOL = "sip";
public static final String HTTP_URI_PROTOCOL = "http";
public static final String MAILTO_URI_PROTOCOL = "mailto";
public static final String SIPS_URI_PROTOCOL = "sips";
public static final String TEL_URI_PROTOCOL = "tel";
public static final String SUPPORTED_100REL = "100rel";
public static final String REMOTE_TARGET = "com.ericsson.ssa.RemoteTarget";
public static final int START_CSEQ = 1;
private static AtomicInteger m_GlobalCallID = new AtomicInteger(1);
private static AtomicLong m_GlobalTag = new AtomicLong(1);
private static ServiceHandler m_ServiceHandler = null;
private static final SipFactoryImpl _instance = new SipFactoryImpl();
private static final Logger _log = LogUtil.SIP_LOGGER.getLogger();
public static final int SIP_RFC_PORT = 5060;
public static final int SIPS_RFC_PORT = 5061;
private static Pattern p =Pattern.compile("^([a-zA-Z])+([\\w+-.])*$");
private static boolean ocsInterop =
Boolean.getBoolean("org.glassfish.sip.ocsInteroperable");
// initial
// value
// needed
// by
// JUnit
// tests
public static SipFactoryImpl getInstance() {
return _instance;
}
public URI createURI(String uri) throws ServletParseException {
try {
// search for the protocol
String protocol = null;
int position = 0;
int cnt = 0;
int length = uri.length();
while ((cnt < length) && (uri.charAt(cnt) != ':')) {
cnt++;
}
protocol = uri.substring(position, cnt);
Matcher m = p.matcher(protocol);
if (!m.find()) {
throw new ServletParseException("URI does not conform to BNF :" + protocol);
}
if (protocol.equals(SIP_URI_PROTOCOL)) {
return new SipURIImpl(SIP_URI_PROTOCOL, uri, cnt + 1);
}
if (protocol.equals(TEL_URI_PROTOCOL)) {
return new TelURLImpl(uri, cnt + 1);
}
if (protocol.equals(SIPS_URI_PROTOCOL)) {
return new SipURIImpl(SIPS_URI_PROTOCOL, uri, cnt + 1);
}
//JSR 289 SipFactory.createURI says, implementation should be able
//to handle any scheme. So, create a GeneralURIImpl.
return new GeneralURIImpl(protocol, uri, cnt + 1);
} catch (Throwable t) {
throw new ServletParseException(t);
}
}
public SipURI createSipURI(String user, String host) {
SipURI uri = new SipURIImpl();
uri.setHost(host);
uri.setUser(user);
return uri;
}
public Address createAddress(String sipAddress)
throws ServletParseException {
return new AddressImpl(sipAddress);
}
public Address createAddress(URI uri) {
return new AddressImpl(uri);
}
public Address createAddress(URI uri, String displayName) {
Address result = new AddressImpl(uri);
result.setDisplayName(displayName);
return result;
}
public SipServletRequestImpl createRequest(
SipApplicationSession appSession, String method, Address from,
Address to) {
return createRequestImpl((SipApplicationSessionImpl) appSession,
method, from, to, false, null);
}
private void createSession(SipServletRequestImpl req,
SipApplicationSessionImpl appSess, String handlerName) {
// creates a new sip session and dialog structure
DialogSet ds = new DialogSet(req.getMethod(), req.getCallId(), req.getFromImpl(),
req.getBeKey(), SipFactoryImpl.isDialogCreational(req.getMethod()));
req.setDialog(ds.getInitialDialogFragment());
DialogFragment d = req.getDialog();
SipSessionBase s = appSess.getSipSessionManager()
.createSipSession(d.getDialogSet(),
req.getToImpl(), appSess, handlerName);
req.setSession(s);
d.getDialogLifeCycle().initUnitOfWork();
}
private URI cleanupFromOrToURI(URI fromOrTo) {
// port, method, maddr, ttl,transport,lr parameters and headers
// need to be removed from the URIs
fromOrTo.removeParameter("port");
fromOrTo.removeParameter("method");
fromOrTo.removeParameter("maddr");
fromOrTo.removeParameter("ttl");
fromOrTo.removeParameter("transport");
fromOrTo.removeParameter("lr");
if (fromOrTo.isSipURI()) {
SipURI sURI = (SipURI)fromOrTo;
Iterator<String> headerNames = sURI.getHeaderNames();
while (headerNames.hasNext()) {
sURI.removeHeader(headerNames.next());
}
}
return fromOrTo;
}
public SipServletRequestImpl createRequestImpl(
SipApplicationSessionImpl appSession, String method, Address from,
Address to, boolean sameCallID, SipServletRequestImpl origRequest) {
if (!method.equals("ACK") && !method.equals("CANCEL")) {
Address fromCopy = (Address) from.clone();
Address toCopy = (Address) to.clone();
// Remove protection, lock up
((AddressImpl) fromCopy).setReadOnly(false);
((AddressImpl) toCopy).setReadOnly(false);
fromCopy.setParameter(AddressImpl.TAG_PARAM, createTag());
toCopy.removeParameter(AddressImpl.TAG_PARAM);
SipServletRequestImpl req = null;
String defaultHandler = null;
if (origRequest == null) {
URI requestURI = (URI) toCopy.getURI().clone();
if ("REGISTER".equals(method) && requestURI.isSipURI()) {
((SipURI)requestURI).setUser(null);
}
req = new SipServletRequestImpl(method, requestURI,
SipFactoryImpl.PROTOCOL_LINE);
req.setInternalRoutingDirective(
SipApplicationRoutingDirective.NEW, null);
req.setBeKey(appSession.getBeKey());
} else {
req = (SipServletRequestImpl) origRequest.clone();
req.setInternalRoutingDirective(
SipApplicationRoutingDirective.CONTINUE, origRequest);
// copy region in case of CONTINUE as well
req.setRegion(origRequest.getRegion());
req.clearApplicationDispatchers();
// The clone will copy the session-case. For the new request
// the session-case should be internal.
req.setSessionCase(SessionCase.INTERNAL);
req.removeSystemHeaders();
req.removeRemoteSettings();
defaultHandler = origRequest.getSessionImpl().getHandler();
}
// set CallID
Header callIDHeader = new SingleLineHeader(Header.CALL_ID, true);
if ((origRequest == null) || !sameCallID) {
callIDHeader.setValue(createCallID(), false);
} else {
callIDHeader.setValue(origRequest.getCallId(), false);
}
req.setHeader(callIDHeader);
URI fromURI = cleanupFromOrToURI(fromCopy.getURI().clone());
fromCopy.setURI(fromURI);
URI toURI = cleanupFromOrToURI(toCopy.getURI().clone());
toCopy.setURI(toURI);
// Put protection, lock again
((AddressImpl) fromCopy).setReadOnly(true);
((AddressImpl) toCopy).setReadOnly(true);
Header toHeader = new SingleLineHeader(Header.TO, true);
toHeader.setAddressValue(toCopy, false);
req.setHeader(toHeader);
Header fromHeader = new SingleLineHeader(Header.FROM, true);
fromHeader.setAddressValue(fromCopy, false);
req.setHeader(fromHeader);
req.setMaxForwards(70);
if (defaultHandler == null) {
defaultHandler = appSession.getCurrentServlet();
}
createSession(req, appSession, defaultHandler);
// MUST be placed after createSession is called
// Add the contact header if the request method is one of
// INVITE, REFER, SUBSCRIBE eller NOTIFY.
if (isDialogCreational(req.getMethod())) {
DialogManager.getInstance().addContact(req);
}
Header cSeqHeader = new SingleLineHeader(Header.CSEQ, true);
cSeqHeader.setValue(Integer.toString(START_CSEQ) + " " +
req.getMethod(), false);
req.setHeader(cSeqHeader);
req.setInitial(true);
List<Layer> layers = LayerHandler.getInstance().getLayers();
req._applicationStack.addAll(layers);
return req;
}
throw new IllegalArgumentException(
"ACK and CANCEL is not allowed to create here.");
}
public SipServletRequestImpl createRequest(
SipApplicationSession appSession, String method, URI from, URI to) {
return createRequest(appSession, method, createAddress(from),
createAddress(to));
}
public SipServletRequestImpl createRequest(
SipApplicationSession appSession, String method, String from, String to)
throws ServletParseException {
return createRequest(appSession, method, createAddress(from),
createAddress(to));
}
@Deprecated
public SipServletRequestImpl createRequest(SipServletRequest origRequest,
boolean sameCallId) {
SipServletRequestImpl origRequestImpl = (SipServletRequestImpl) origRequest;
SipServletRequestImpl req;
req = createRequestImpl((SipApplicationSessionImpl) origRequest.getApplicationSession(),
origRequest.getMethod(), origRequestImpl.getFromImpl(),
origRequestImpl.getToImpl(), sameCallId, origRequestImpl);
// HG54598, copy the Route header
Header route = origRequestImpl.getRawHeader(Header.ROUTE);
if (route != null) {
req.setHeader((Header) route.clone());
}
// HH20252, copy the Contact header
Header contact = origRequestImpl.getRawHeader(Header.CONTACT);
if ((contact != null) && req.getMethod().equals("REGISTER")) {
req.setHeader((Header) contact.clone());
}
return req;
}
public SipApplicationSessionImpl createApplicationSession() {
/*
* This method should never be called and therefore always returns null.
* We only call createApplicationSession() on the context specific
* facade of this singleton, which delegates the creation of the
* SipApplicationSession to the context's associated session manager
*/
return null;
}
/**
* Returns a unique to tag.
*/
public String createTag() {
StringBuilder sb = new StringBuilder(
Long.toString(System.currentTimeMillis(),
36)).append('-').append(Long.toString(
m_GlobalTag.getAndIncrement(), 36));
return sb.toString();
}
/**
* Returns a unique Call-ID.
*/
public String createCallID() {
if (ocsInterop) {
return createCallID32Max();
}
int id = m_GlobalCallID.getAndIncrement();
long r = Math.abs(new Random().nextLong());
StringBuilder sb = new StringBuilder();
sb.append(SipBindingResolver.instance().getCurrentPublicHost());
sb.append('_').append(id).append('_').append(r);
return sb.toString();
}
/**
* Returns a unique Call-ID. The maximum length of the call-id should
* be 32 for OCS. This method returns the call-id with max length of 32.
* RFC 3261 indicate that the hostname should be part of the callid. However
* if we add hostname as is, most of 32 length will be taken away by
* hostname (especially when ipv6 is used). The remaining parts of the callid
* may not guarantee uniqueness, especially when multiple instances are in
* the same box.
* So the callid will be
* (hashcode of host address)_(an incremented number)_(randon number)
*/
public String createCallID32Max() {
if (_log.isLoggable(Level.FINER)) {
_log.log(Level.FINER, "Creating call id with max 32 length");
}
int h = Math.abs(SipBindingResolver.instance().getCurrentPublicHost().hashCode());
int id = m_GlobalCallID.getAndIncrement();
int r = Math.abs(new Random().nextInt());
StringBuilder sb = new StringBuilder();
sb.append(h).append('_').append(id).append('_').append(r);
return sb.toString();
}
public Parameterable createParameterable(String s)
throws ServletParseException {
return ParameterableImpl.parse(s);
}
public ServiceHandler getServiceHandler() {
return m_ServiceHandler;
}
public void setServiceHandler(ServiceHandler s) {
m_ServiceHandler = s;
}
// As per section 4.1.3 of JSR 289, determine if contact is allowed
// to be added in the servlet.
public static boolean contactAllowedFromServlet(SipServletResponse resp) {
int status = resp.getStatus();
String method = resp.getMethod();
if (((status >= 300) && (status < 400)) || (status == 485)) {
return true;
}
if ("REGISTER".equals(method)) {
return true;
}
if ("OPTIONS".equals(method)) {
return status == 200;
}
return false;
}
/**
* Check if the Contact header can be added (mandatory or optional in RFC) to
* the response, according to the followning RFC's
* <ul>
* <li>RFC 3311</li>
* <li>RFC 3262</li>
* <li>RFC 3428</li>
* <li>RFC 3261</li>
* <li>RFC 3265</li>
* <li>RFC 3515 </li>
* </ul>
*
* @param resp
* The status code and SIP method will be used when performing the
* check.
* @return true if the Contact header can be added to the response.
*/
public static HeaderRequirement getContactRequirement(
SipServletResponse resp) {
int status = resp.getStatus();
String method = resp.getMethod();
if ("BYE".equals(method)) // RFC 3261
{
if (((status >= 300) && (status < 400)) || (status == 485)) {
return HeaderRequirement.OPTIONAL;
}
}
if ("INVITE".equals(method)) // RFC 3261
{
if ((status > 100) && (status < 200)) {
return HeaderRequirement.OPTIONAL;
}
if ((status >= 200) && (status < 300)) {
return HeaderRequirement.MANDATORY;
}
if (((status >= 300) && (status < 400)) || (status == 485)) {
return HeaderRequirement.OPTIONAL;
}
}
if ("OPTION".equals(method) || "REGISTER".equals(method)) // RFC 3261
{
if (((status >= 200) && (status < 400)) || (status == 485)) {
return HeaderRequirement.OPTIONAL;
}
}
if ("SUBSCRIBE".equals(method)) // RFC-3265
{
if ((status >= 100) && (status < 200)) {
return HeaderRequirement.OPTIONAL;
}
if ((status >= 200) && (status < 400)) {
return HeaderRequirement.MANDATORY;
}
if (status == 485) {
return HeaderRequirement.OPTIONAL;
}
}
if ("NOTIFY".equals(method)) // RFC-3265
{
if ((status >= 100) && (status < 300)) {
return HeaderRequirement.OPTIONAL;
}
if ((status >= 300) && (status < 400)) {
return HeaderRequirement.MANDATORY;
}
if (status == 485) {
return HeaderRequirement.OPTIONAL;
}
}
if ("REFER".equals(method)) // RFC 3515
{
if ((status >= 200) && (status < 300)) {
return HeaderRequirement.MANDATORY;
}
if ((status >= 300) && (status < 700)) {
return HeaderRequirement.OPTIONAL;
}
}
if ("UPDATE".equals(method)) // RFC 3311
{
if ((status >= 100) && (status < 200)) {
return HeaderRequirement.OPTIONAL;
}
if ((status >= 200) && (status < 300)) {
return HeaderRequirement.MANDATORY;
}
if (((status >= 300) && (status < 400)) || (status == 485)) {
return HeaderRequirement.OPTIONAL;
}
}
if ("MESSAGE".equals(method)) // RFC 3428
{
if (((status >= 300) && (status < 400)) || (status == 485)) {
return HeaderRequirement.OPTIONAL;
}
}
if ("PRACK".equals(method)) // RFC 3262
{
if (((status >= 300) && (status < 400)) || (status == 485)) {
return HeaderRequirement.OPTIONAL;
}
}
return HeaderRequirement.NOT_APPLICAPLE;
}
public static boolean isDialogCreational(String method) {
return method.equals("SUBSCRIBE") || method.equals("INVITE") ||
method.equals("REFER") || method.equals("NOTIFY");
}
public static boolean initialRequestPossible(String method) {
return !(method.equals("PRACK") ||
method.equals("BYE") || method.equals("UPDATE") ||
method.equals("ACK") || method.equals("INFO"));
}
public static boolean isContactMandatory(SipServletRequest req) {
return isDialogCreational(req.getMethod()) ||
req.getMethod().equals("UPDATE");
}
public AuthInfo createAuthInfo() {
return new AuthInfoImpl();
}
public SipApplicationSession createApplicationSessionByAppName(
String sipAppName) {
// This method is only implemented by the Facade
return null;
}
public SipApplicationSession createApplicationSessionByKey(
String sipApplicationKey) {
// This method is only implemented by the Facade
return null;
}
}