All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mobicents.servlet.sip.JainSipUtils Maven / Gradle / Ivy

/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2014, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see 
 */

package org.mobicents.servlet.sip;


import gov.nist.javax.sip.ListeningPointExt;
import gov.nist.javax.sip.TransactionExt;
import gov.nist.javax.sip.header.extensions.ReferredByHeader;
import gov.nist.javax.sip.header.extensions.SessionExpiresHeader;
import gov.nist.javax.sip.header.ims.PAssertedIdentityHeader;
import gov.nist.javax.sip.header.ims.PAssociatedURIHeader;
import gov.nist.javax.sip.header.ims.PMediaAuthorizationHeader;
import gov.nist.javax.sip.header.ims.PVisitedNetworkIDHeader;
import gov.nist.javax.sip.header.ims.PathHeader;
import gov.nist.javax.sip.header.ims.PrivacyHeader;
import gov.nist.javax.sip.header.ims.SecurityClientHeader;
import gov.nist.javax.sip.header.ims.SecurityServerHeader;
import gov.nist.javax.sip.header.ims.SecurityVerifyHeader;
import gov.nist.javax.sip.header.ims.ServiceRouteHeader;
import gov.nist.javax.sip.message.SIPMessage;
import gov.nist.javax.sip.stack.SIPClientTransaction;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;

import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.ObjectInUseException;
import javax.sip.Transaction;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.AcceptEncodingHeader;
import javax.sip.header.AcceptHeader;
import javax.sip.header.AcceptLanguageHeader;
import javax.sip.header.AlertInfoHeader;
import javax.sip.header.AllowEventsHeader;
import javax.sip.header.AllowHeader;
import javax.sip.header.AuthenticationInfoHeader;
import javax.sip.header.AuthorizationHeader;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.CallInfoHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.ContentDispositionHeader;
import javax.sip.header.ContentEncodingHeader;
import javax.sip.header.ContentLanguageHeader;
import javax.sip.header.ContentLengthHeader;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.DateHeader;
import javax.sip.header.ErrorInfoHeader;
import javax.sip.header.EventHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.InReplyToHeader;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.MimeVersionHeader;
import javax.sip.header.MinExpiresHeader;
import javax.sip.header.OrganizationHeader;
import javax.sip.header.PriorityHeader;
import javax.sip.header.ProxyAuthenticateHeader;
import javax.sip.header.ProxyAuthorizationHeader;
import javax.sip.header.ProxyRequireHeader;
import javax.sip.header.RAckHeader;
import javax.sip.header.RSeqHeader;
import javax.sip.header.ReasonHeader;
import javax.sip.header.RecordRouteHeader;
import javax.sip.header.ReferToHeader;
import javax.sip.header.ReplyToHeader;
import javax.sip.header.RequireHeader;
import javax.sip.header.RetryAfterHeader;
import javax.sip.header.RouteHeader;
import javax.sip.header.ServerHeader;
import javax.sip.header.SubjectHeader;
import javax.sip.header.SubscriptionStateHeader;
import javax.sip.header.SupportedHeader;
import javax.sip.header.TimeStampHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.UnsupportedHeader;
import javax.sip.header.UserAgentHeader;
import javax.sip.header.ViaHeader;
import javax.sip.header.WWWAuthenticateHeader;
import javax.sip.header.WarningHeader;
import javax.sip.message.Message;
import javax.sip.message.Request;

import org.apache.log4j.Logger;
import org.mobicents.servlet.sip.core.MobicentsExtendedListeningPoint;
import org.mobicents.servlet.sip.core.SipApplicationDispatcher;
import org.mobicents.servlet.sip.core.SipNetworkInterfaceManager;
import org.mobicents.servlet.sip.core.dispatchers.MessageDispatcher;
import org.mobicents.servlet.sip.core.session.MobicentsSipSession;
import org.mobicents.servlet.sip.message.SipFactoryImpl;
import org.mobicents.servlet.sip.message.SipFactoryImpl.NamesComparator;
import org.mobicents.servlet.sip.startup.StaticServiceHolder;

/**
 * 
 * Various helpful utilities to map jain sip abstractions. 
 * 
 * @author mranga
 * @author Jean Deruelle
 */
public final class JainSipUtils {
	private static final Logger logger = Logger.getLogger(JainSipUtils.class);
	
	/**
     * The maximum int value that could correspond to a port nubmer.
     */
    public static final int    MAX_PORT_NUMBER = 65535;

    /**
     * The minimum int value that could correspond to a port nubmer bindable
     * by the SIP Communicator.
     */
    public static final int    MIN_PORT_NUMBER = 1024;
    
	
//    private static final transient Logger logger = Logger.getLogger(JainSipUtils.class);

	public static final String GLOBAL_IPADDRESS = "0.0.0.0";
	
	// methods where a contact header is mandatory
	public static final Set CONTACT_HEADER_METHODS = new TreeSet(
			new NamesComparator());
	
	static {		
		CONTACT_HEADER_METHODS.add(Request.INVITE);
		// Issue 1412 http://code.google.com/p/mobicents/issues/detail?id=1412 
		// Contact header is added to REGISTER request by container but javadoc says
		// "The container is responsible for assigning the request appropriate Call-ID and CSeq 
		// headers, as well as Contact header if the method is not REGISTER." so commenting out
//		CONTACT_HEADER_METHODS.add(Request.REGISTER);
		CONTACT_HEADER_METHODS.add(Request.SUBSCRIBE);
		CONTACT_HEADER_METHODS.add(Request.NOTIFY);
		CONTACT_HEADER_METHODS.add(Request.REFER);
		CONTACT_HEADER_METHODS.add(Request.UPDATE);
		// http://code.google.com/p/sipservlets/issues/detail?id=172 
		CONTACT_HEADER_METHODS.add(Request.OPTIONS);
		// https://github.com/Mobicents/sip-servlets/issues/48 Added to support RCS
		CONTACT_HEADER_METHODS.add(Request.MESSAGE);
	}
	
	public static final Set DIALOG_CREATING_METHODS = new TreeSet(
			new NamesComparator());
	
	public static final Set DIALOG_TERMINATING_METHODS = new TreeSet(
			new NamesComparator());

	static {		
		DIALOG_CREATING_METHODS.add(Request.INVITE);
		DIALOG_CREATING_METHODS.add(Request.SUBSCRIBE);
		DIALOG_CREATING_METHODS.add(Request.REFER);
		DIALOG_TERMINATING_METHODS.add(Request.CANCEL);
		DIALOG_TERMINATING_METHODS.add(Request.BYE);
	}
	
	public static final String INITIAL_REMOTE_ADDR_HEADER_NAME = "MSS_Initial_Remote_Addr";
	public static final String INITIAL_REMOTE_PORT_HEADER_NAME = "MSS_Initial_Remote_Port";
	public static final String INITIAL_REMOTE_TRANSPORT_HEADER_NAME = "MSS_Initial_Remote_Transport";
	
	/**
	 * List of headers that ARE system at all times
	 */
	public static final Set SYSTEM_HEADERS = new HashSet();
	static {

		// From and To are not system header in requests except for their tag 
//		SYSTEM_HEADERS.add(FromHeader.NAME);
//		SYSTEM_HEADERS.add(ToHeader.NAME);
		SYSTEM_HEADERS.add(CallIdHeader.NAME);
		SYSTEM_HEADERS.add(CSeqHeader.NAME);
		SYSTEM_HEADERS.add(ViaHeader.NAME);
		SYSTEM_HEADERS.add(RouteHeader.NAME);
		SYSTEM_HEADERS.add(RecordRouteHeader.NAME);
		SYSTEM_HEADERS.add(PathHeader.NAME);
		// This is system in messages other than REGISTER!!! ContactHeader.NAME
		// Contact is a system header field in messages other than REGISTER
		// requests and responses, 3xx and 485 responses, and 200/OPTIONS
		// responses. Additionally, for containers implementing the reliable
		// provisional responses extension, RAck and RSeq are considered system
		// headers also.
		SYSTEM_HEADERS.add(RSeqHeader.NAME);
		SYSTEM_HEADERS.add(RAckHeader.NAME);
		//custom header used by Mobicents Sip Servlets and not allowed to be overriden by apps
		SYSTEM_HEADERS.add(INITIAL_REMOTE_ADDR_HEADER_NAME);
		SYSTEM_HEADERS.add(INITIAL_REMOTE_PORT_HEADER_NAME);
		SYSTEM_HEADERS.add(INITIAL_REMOTE_TRANSPORT_HEADER_NAME);
	}

	public static final Set ADDRESS_HEADER_NAMES = new HashSet();

	static {

		// Section 4.1 The baseline SIP specification defines the following set of header
		// fields that conform to this grammar: From, To, Contact, Route,
		// Record-Route, Reply-To, Alert-Info, Call-Info, and Error-Info
		// The SipServletMessage interface defines a set of methods which operate 
		// on any address header field (see section 5.4.1 Parameterable and Address Header Fields ). 
		// This includes the RFC 3261 defined header fields listed above as well as extension headers 
		// such as Refer-To [refer] and P-Asserted-Identity [privacy]. 

		ADDRESS_HEADER_NAMES.add(FromHeader.NAME);
		ADDRESS_HEADER_NAMES.add(ToHeader.NAME);
		ADDRESS_HEADER_NAMES.add(ContactHeader.NAME);
		ADDRESS_HEADER_NAMES.add(RouteHeader.NAME);
		ADDRESS_HEADER_NAMES.add(RecordRouteHeader.NAME);
		ADDRESS_HEADER_NAMES.add(ReplyToHeader.NAME);
		ADDRESS_HEADER_NAMES.add(AlertInfoHeader.NAME);
		ADDRESS_HEADER_NAMES.add(CallInfoHeader.NAME);
		ADDRESS_HEADER_NAMES.add(ErrorInfoHeader.NAME);
		ADDRESS_HEADER_NAMES.add(ReferToHeader.NAME);
		ADDRESS_HEADER_NAMES.add(PAssertedIdentityHeader.NAME);
			
	}

	public static final Set PARAMETERABLE_HEADER_NAMES = new HashSet();

	static {

		// All of the Address header fields are Parameterable, including Contact, From, To, Route, Record-Route, and Reply-To. 
		// In addition, the header fields Accept, Accept-Encoding, Alert-Info, 
		// Call-Info, Content-Disposition, Content-Type, Error-Info, Retry-After and Via are also Parameterable. 
		PARAMETERABLE_HEADER_NAMES.add(FromHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(ToHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(ContactHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(RouteHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(RecordRouteHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(ReplyToHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(AcceptHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(AcceptEncodingHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(AlertInfoHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(CallInfoHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(ContentDispositionHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(ContentTypeHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(ErrorInfoHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(RetryAfterHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(ViaHeader.NAME);
		// https://code.google.com/p/sipservlets/issues/detail?id=239
		PARAMETERABLE_HEADER_NAMES.add(WWWAuthenticateHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(ProxyAuthenticateHeader.NAME);
		PARAMETERABLE_HEADER_NAMES.add(AuthorizationHeader.NAME);
        PARAMETERABLE_HEADER_NAMES.add(ProxyAuthorizationHeader.NAME);
        // https://code.google.com/p/sipservlets/issues/detail?id=260
        PARAMETERABLE_HEADER_NAMES.add(EventHeader.NAME);
        // https://github.com/Mobicents/sip-servlets/issues/45
        PARAMETERABLE_HEADER_NAMES.add(SubscriptionStateHeader.NAME);
	}

	
	public static final Map HEADER_COMPACT_2_FULL_NAMES_MAPPINGS = new HashMap();

	{ // http://www.iana.org/assignments/sip-parameters
		// Header Name compact Reference
		// ----------------- ------- ---------
		// Call-ID i [RFC3261]
		// From f [RFC3261]
		// To t [RFC3261]
		// Via v [RFC3261]
		// =========== NON SYSTEM HEADERS ========
		// Contact m [RFC3261] <-- Possibly system header
		// Accept-Contact a [RFC3841]
		// Allow-Events u [RFC3265]
		// Content-Encoding e [RFC3261]
		// Content-Length l [RFC3261]
		// Content-Type c [RFC3261]
		// Event o [RFC3265]
		// Identity y [RFC4474]
		// Identity-Info n [RFC4474]
		// Refer-To r [RFC3515]
		// Referred-By b [RFC3892]
		// Reject-Contact j [RFC3841]
		// Request-Disposition d [RFC3841]
		// Session-Expires x [RFC4028]
		// Subject s [RFC3261]
		// Supported k [RFC3261]

		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("i", CallIdHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("f", FromHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("t", ToHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("v", ViaHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("m", ContactHeader.NAME);
		// headerCompact2FullNamesMappings.put("a",); // Where is this header?
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("u", AllowEventsHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("e", ContentEncodingHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("l", ContentLengthHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("c", ContentTypeHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("o", EventHeader.NAME);
		// headerCompact2FullNamesMappings.put("y", IdentityHeader); // Where is
		// sthis header?
		// headerCompact2FullNamesMappings.put("n",IdentityInfoHeader );
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("r", ReferToHeader.NAME);
		 HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("b", ReferredByHeader.NAME);
		// headerCompact2FullNamesMappings.put("j", RejectContactHeader);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("d", ContentDispositionHeader.NAME);
		 HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("x", SessionExpiresHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("s", SubjectHeader.NAME);
		HEADER_COMPACT_2_FULL_NAMES_MAPPINGS.put("k", SupportedHeader.NAME);
	}

	public static final Map HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS = new HashMap();

	static { // http://www.iana.org/assignments/sip-parameters
		// Header Name compact Reference
		// ----------------- ------- ---------
		// Call-ID i [RFC3261]
		// From f [RFC3261]
		// To t [RFC3261]
		// Via v [RFC3261]
		// =========== NON SYSTEM HEADERS ========
		// Contact m [RFC3261] <-- Possibly system header
		// Accept-Contact a [RFC3841]
		// Allow-Events u [RFC3265]
		// Content-Encoding e [RFC3261]
		// Content-Length l [RFC3261]
		// Content-Type c [RFC3261]
		// Event o [RFC3265]
		// Identity y [RFC4474]
		// Identity-Info n [RFC4474]
		// Refer-To r [RFC3515]
		// Referred-By b [RFC3892]
		// Reject-Contact j [RFC3841]
		// Request-Disposition d [RFC3841]
		// Session-Expires x [RFC4028]
		// Subject s [RFC3261]
		// Supported k [RFC3261]

		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(CallIdHeader.NAME, "i");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(FromHeader.NAME, "f");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(ToHeader.NAME, "t");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(ViaHeader.NAME, "v");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(ContactHeader.NAME, "m");
		// headerFull2CompactNamesMappings.put(,"a"); // Where is this header?
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(AllowEventsHeader.NAME, "u");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(ContentEncodingHeader.NAME, "e");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(ContentLengthHeader.NAME, "l");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(ContentTypeHeader.NAME, "c");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(EventHeader.NAME, "o");
		// headerCompact2FullNamesMappings.put(IdentityHeader,"y"); // Where is
		// sthis header?
		// headerCompact2FullNamesMappings.put(IdentityInfoHeader ,"n");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(ReferToHeader.NAME, "r");
		// headerCompact2FullNamesMappings.put(ReferedByHeader,"b");
		// headerCompact2FullNamesMappings.put(RejectContactHeader,"j");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(ContentDispositionHeader.NAME, "d");
		// headerCompact2FullNamesMappings.put(SessionExpiresHeader,"x");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(SubjectHeader.NAME, "s");
		HEADER_FULL_TO_COMPACT_NAMES_MAPPINGS.put(SupportedHeader.NAME, "k");
	}
	
	public static final Set IANA_ALLOWED_CONTENT_TYPES = new HashSet();	

	static {

		// All of the Address header fields are Parameterable, including Contact, From, To, Route, Record-Route, and Reply-To. 
		// In addition, the header fields Accept, Accept-Encoding, Alert-Info, 
		// Call-Info, Content-Disposition, Content-Type, Error-Info, Retry-After and Via are also Parameterable. 
		IANA_ALLOWED_CONTENT_TYPES.add("application");
		IANA_ALLOWED_CONTENT_TYPES.add("audio");
		IANA_ALLOWED_CONTENT_TYPES.add("example");
		IANA_ALLOWED_CONTENT_TYPES.add("image");
		IANA_ALLOWED_CONTENT_TYPES.add("message");
		IANA_ALLOWED_CONTENT_TYPES.add("model");
		IANA_ALLOWED_CONTENT_TYPES.add("multipart");
		IANA_ALLOWED_CONTENT_TYPES.add("text");
		IANA_ALLOWED_CONTENT_TYPES.add("video");
			
	}

	// we don't have any other choice as to maintain a static list of multi value headers
	// because checking for , for the values as a delimiter won't work for WWW-Authenticate header which is not a multivalue header
	// but contains multiple , 
	public static final Set SINGLETON_HEADER_NAMES = new HashSet();
	static {
		SINGLETON_HEADER_NAMES.add(FromHeader.NAME);
		SINGLETON_HEADER_NAMES.add(ToHeader.NAME);
		SINGLETON_HEADER_NAMES.add(CSeqHeader.NAME);
		SINGLETON_HEADER_NAMES.add(CallIdHeader.NAME);
		SINGLETON_HEADER_NAMES.add(MaxForwardsHeader.NAME);
		SINGLETON_HEADER_NAMES.add(ContentLengthHeader.NAME);		
		SINGLETON_HEADER_NAMES.add(ContentDispositionHeader.NAME);
		SINGLETON_HEADER_NAMES.add(ContentTypeHeader.NAME);
		SINGLETON_HEADER_NAMES.add(ContentLengthHeader.NAME);
		SINGLETON_HEADER_NAMES.add(ContentTypeHeader.NAME);
		SINGLETON_HEADER_NAMES.add(DateHeader.NAME);
		SINGLETON_HEADER_NAMES.add(ContentTypeHeader.NAME);
		SINGLETON_HEADER_NAMES.add(ExpiresHeader.NAME);
		SINGLETON_HEADER_NAMES.add(MinExpiresHeader.NAME);
		SINGLETON_HEADER_NAMES.add(MimeVersionHeader.NAME);
		SINGLETON_HEADER_NAMES.add(MinExpiresHeader.NAME);
		SINGLETON_HEADER_NAMES.add(OrganizationHeader.NAME);
		SINGLETON_HEADER_NAMES.add(PriorityHeader.NAME);
		SINGLETON_HEADER_NAMES.add(ReplyToHeader.NAME);
		SINGLETON_HEADER_NAMES.add(RetryAfterHeader.NAME);
		SINGLETON_HEADER_NAMES.add(PriorityHeader.NAME);
		SINGLETON_HEADER_NAMES.add(ServerHeader.NAME);
		SINGLETON_HEADER_NAMES.add(SubjectHeader.NAME);
		SINGLETON_HEADER_NAMES.add(TimeStampHeader.NAME);
		SINGLETON_HEADER_NAMES.add(UserAgentHeader.NAME);
		SINGLETON_HEADER_NAMES.add(WWWAuthenticateHeader.NAME);
		// Fix by andrew miller 
		// Issue 1547 : Can't add a Proxy-Authorization using SipServletMessage.addHeader
		// those headers are singleton headers not list headers
		SINGLETON_HEADER_NAMES.add(ProxyAuthenticateHeader.NAME);		
		SINGLETON_HEADER_NAMES.add(ProxyAuthorizationHeader.NAME);
		// Issue 2798 : Can't add a Authorization using SipServletMessage.addHeader
		// those headers are singleton headers not list headers
		SINGLETON_HEADER_NAMES.add(AuthorizationHeader.NAME);
		// Same thing for Issue 2578 
		SINGLETON_HEADER_NAMES.add(AuthenticationInfoHeader.NAME);
	}	
	
	// we don't have any other choice as to maintain a static list of multi value headers
	// because checking for , for the values as a delimiter won't work for WWW-Authenticate header which is not a multivalue header
	// but contains multiple , 
	public static final Set LIST_HEADER_NAMES = new HashSet();
	static {
		LIST_HEADER_NAMES.add(AcceptEncodingHeader.NAME);
		LIST_HEADER_NAMES.add(AcceptLanguageHeader.NAME);
		LIST_HEADER_NAMES.add(AcceptHeader.NAME);
		LIST_HEADER_NAMES.add(AlertInfoHeader.NAME);
		LIST_HEADER_NAMES.add(AllowEventsHeader.NAME);
		LIST_HEADER_NAMES.add(AllowHeader.NAME);				
		LIST_HEADER_NAMES.add(CallInfoHeader.NAME);
		LIST_HEADER_NAMES.add(ContactHeader.NAME);
		LIST_HEADER_NAMES.add(ContentEncodingHeader.NAME);
		LIST_HEADER_NAMES.add(ContentLanguageHeader.NAME);
		LIST_HEADER_NAMES.add(ErrorInfoHeader.NAME);
		LIST_HEADER_NAMES.add(InReplyToHeader.NAME);
		LIST_HEADER_NAMES.add(ProxyRequireHeader.NAME);
		LIST_HEADER_NAMES.add(ReasonHeader.NAME);
		LIST_HEADER_NAMES.add(RecordRouteHeader.NAME);
		LIST_HEADER_NAMES.add(RequireHeader.NAME);
		LIST_HEADER_NAMES.add(RouteHeader.NAME);
		LIST_HEADER_NAMES.add(SupportedHeader.NAME);
		LIST_HEADER_NAMES.add(UnsupportedHeader.NAME);
		LIST_HEADER_NAMES.add(ViaHeader.NAME);
		LIST_HEADER_NAMES.add(WarningHeader.NAME);
		LIST_HEADER_NAMES.add(PAssertedIdentityHeader.NAME);
		LIST_HEADER_NAMES.add(PAssociatedURIHeader.NAME);
		LIST_HEADER_NAMES.add(PathHeader.NAME);
		LIST_HEADER_NAMES.add(PMediaAuthorizationHeader.NAME);
		LIST_HEADER_NAMES.add(PrivacyHeader.NAME);
		LIST_HEADER_NAMES.add(PVisitedNetworkIDHeader.NAME);
		LIST_HEADER_NAMES.add(SecurityClientHeader.NAME);
		LIST_HEADER_NAMES.add(SecurityServerHeader.NAME);
		LIST_HEADER_NAMES.add(SecurityVerifyHeader.NAME);
		LIST_HEADER_NAMES.add(ServiceRouteHeader.NAME);
	}	

	private static final String[] ALLOWED_ADDRESS_SCHEMES = {"sip","sips","tel","tels"};
	
	public static final int MAX_FORWARD_HEADER_VALUE = 70;

	// never instantiate a utility class : Enforce noninstantiability with private constructor
    private JainSipUtils() {
    	throw new AssertionError();    	
    }
	
	// RFC 1918 address spaces
	public static int getAddressOutboundness(String address) {
		if(address.startsWith("127.0")) return 0;
		if(address.startsWith("192.168")) return 1;
		if(address.startsWith("10.")) return 2;
		if(address.startsWith("172.16") || address.startsWith("172.17") || address.startsWith("172.18") 
				|| address.startsWith("172.19") || address.startsWith("172.20") || address.startsWith("172.21") 
				|| address.startsWith("172.22") || address.startsWith("172.23") || address.startsWith("172.24")
				|| address.startsWith("172.25") || address.startsWith("172.26") || address.startsWith("172.27")
				|| address.startsWith("172.28") || address.startsWith("172.29") || address.startsWith("172.30")
				|| address.startsWith("172.31")) return 3;
		if(address.indexOf(".")>0) return 4; // match IPv4 addresses heuristically
		return -1; // matches IPv6 or something malformed;
	}
	public static String getMostOutboundAddress(List addresses) {
		// getIpAddresses() returns [0:0:0:0:0:0:0:1, 127.0.0.1, 2001:0:d5c7:a2ca:3065:1735:3f57:fe71, fe80:0:0:0:3065:1735:3f57:fe71%15, 192.168.1.142, fe80:0:0:0:0:5efe:c0a8:18e%21]
		// IPv6 addresses are not good for default value
		String bestAddr = "127.0.0.1"; // The default is something completely fails
		int bestAddrOutboundness = -2;
		for(String address:addresses)  {
			int addrOutboundness = getAddressOutboundness(address);
			if(addrOutboundness>bestAddrOutboundness) {
				bestAddr = address;
				bestAddrOutboundness = addrOutboundness;
			}
		}
		return bestAddr;
	}
	
	/**
	 * 
	 * @param sipNetworkInterfaceManager
	 * @param transport
	 * @param branch
	 * @return
	 */
	public static ViaHeader createViaHeader(
			SipNetworkInterfaceManager sipNetworkInterfaceManager, Request request, String branch, String outboundInterface) {		
		MobicentsExtendedListeningPoint listeningPoint = null;
		listeningPoint = findListeningPoint(sipNetworkInterfaceManager,
				request, outboundInterface);
		boolean usePublicAddress = findUsePublicAddress(
				sipNetworkInterfaceManager, request, listeningPoint);
		return listeningPoint.createViaHeader(branch, usePublicAddress);		
    }
	
	/**
	 * 
	 * @param sipNetworkInterfaceManager
	 * @param transport
	 * @param branch
	 * @return
	 */
	public static String createBranch(String appSessionId, String appname) {
	    // https://code.google.com/p/sipservlets/issues/detail?id=269
		return createBranch(appSessionId, appname, UUID.randomUUID().toString());
    }
	
	public static String createBranch(String appSessionId, String appname, String random) {
		return MessageDispatcher.BRANCH_MAGIC_COOKIE + appSessionId + "_" + appname + "_" + random;		
    }
	 
	/**
	 * 
	 * @param sipNetworkInterfaceManager
	 * @param transport
	 * @return
	 */
	public static ContactHeader createContactHeader(SipNetworkInterfaceManager sipNetworkInterfaceManager, Request request, String displayName, String userName, String outboundInterface) {		
		MobicentsExtendedListeningPoint listeningPoint = 
			findListeningPoint(sipNetworkInterfaceManager, request, outboundInterface);
		
		boolean usePublicAddress = findUsePublicAddress(
				sipNetworkInterfaceManager, request, listeningPoint);
		ContactHeader ch = null;
		if(outboundInterface!=null){
			ch = listeningPoint.createContactHeader(displayName, userName, usePublicAddress, outboundInterface);
		} else {
			ch = listeningPoint.createContactHeader(displayName, userName, usePublicAddress);
		}
		if(StaticServiceHolder.sipStandardService.isMd5ContactUserPart()) {
			CallIdHeader callId = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
			String username = getHash(callId.getCallId().getBytes());
			SipURI uri = (SipURI)ch.getAddress().getURI();
			try {
				uri.setUser(username);
				ch.getAddress().setDisplayName(null);
			} catch (ParseException e) {
				throw new IllegalStateException("Can't create contact header user part with MD5", e);
			}
		}
		
		return ch;
	}

	public static MobicentsExtendedListeningPoint findListeningPoint(
			SipNetworkInterfaceManager sipNetworkInterfaceManager,
			Request request, String outboundInterface) {
		
		MobicentsExtendedListeningPoint listeningPoint;
		
		if(outboundInterface == null) {
			String transport = findTransport(request);
			listeningPoint = sipNetworkInterfaceManager.findMatchingListeningPoint(transport, false);
		} else {
			javax.sip.address.SipURI outboundInterfaceURI = null;			
			try {
				outboundInterfaceURI = (javax.sip.address.SipURI) SipFactoryImpl.addressFactory.createURI(outboundInterface);
			} catch (ParseException e) {
				throw new IllegalArgumentException("couldn't parse the outbound interface " + outboundInterface, e);
			}			
			listeningPoint = sipNetworkInterfaceManager.findMatchingListeningPoint(outboundInterfaceURI, false);
		}
		return listeningPoint;
	}
	
	private static ThreadLocal localDigest = new ThreadLocal();
	private static String getHash(byte[] b) {
		MessageDigest md = localDigest.get();
		if(md == null) {
			try {
				md = MessageDigest.getInstance("MD5");
			} catch (NoSuchAlgorithmException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			localDigest.set(md);
		}
		md.reset();
		md.update(b);
		
	    byte[] hash = md.digest();
	    StringBuilder sb = new StringBuilder();
	    for(int i=0; i> 4) & 0x0F ) );
	      sb.append(Integer.toHexString( hash[i] & 0x0F ) );
	    }
	    String rv = sb.toString();
	    return rv;
	}

	/**
	 * 
	 * @param sipProviders
	 * @param transport
	 * @return
	 */
	public static javax.sip.address.SipURI createRecordRouteURI(SipNetworkInterfaceManager sipNetworkInterfaceManager, Message message) {
		String transport = findTransport(message);		
		return createRecordRouteURI(sipNetworkInterfaceManager, message, transport);
	}
	
	public static javax.sip.address.SipURI createRecordRouteURI(SipNetworkInterfaceManager sipNetworkInterfaceManager, Message message, String transport) {	
		MobicentsExtendedListeningPoint listeningPoint = sipNetworkInterfaceManager.findMatchingListeningPoint(transport, false);
		boolean usePublicAddress = findUsePublicAddress(
				sipNetworkInterfaceManager, message, listeningPoint);
		return listeningPoint.createRecordRouteURI(usePublicAddress);
	}

	/**
	 * 
	 * @param sipNetworkInterfaceManager
	 * @param request
	 * @param listeningPoint
	 * @return
	 */
	public static boolean findUsePublicAddress(
			SipNetworkInterfaceManager sipNetworkInterfaceManager,
			Message message, MobicentsExtendedListeningPoint listeningPoint) {
		boolean usePublicAddress = false;
		if(listeningPoint.isUseStaticAddress()) {
			usePublicAddress = true;
		} else if(listeningPoint.getGlobalIpAddress() != null) {
			usePublicAddress = sipNetworkInterfaceManager.findUsePublicAddress(message);
		}
		return usePublicAddress;
	}

	/**
	 * 
	 * @param request
	 * @return
	 */
	public static String findTransport(Message message) {		
		if(message == null) {
			return ListeningPoint.UDP;
		}
		// check if the transport is present in the message application data for maximizing perf
		String transport = (String)((SIPMessage)message).getApplicationData();
		if(transport != null) {			
			if(logger.isDebugEnabled()) {
				logger.debug("AppData Transport " + transport);
			}
			return transport;
		}
		//if the request uri doesn't have any param, the request can still be on TCP so we check the topmost via header
		ViaHeader topmostViaHeader = (ViaHeader) message.getHeader(ViaHeader.NAME);
		if(topmostViaHeader != null) {
			String viaTransport = topmostViaHeader.getTransport();
			if(viaTransport != null && viaTransport.length() > 0) {
				transport = viaTransport;
				if(logger.isDebugEnabled()) {
					logger.debug("Via Transport " + transport);
				}
			}
		}
		
		if(transport == null && message instanceof Request) {
			// https://github.com/Mobicents/sip-servlets/issues/62
			transport = findRouteOrRequestUriTransport((Request) message);
		}
			
		// storing the transport is present in the message application data for maximizing perf 
		// in speeding up the retrieval later on
		((SIPMessage)message).setApplicationData(transport);
		return transport;
	}
	
	// https://github.com/Mobicents/sip-servlets/issues/62
	public static String findRouteOrRequestUriTransport(Request request) {
		RouteHeader route = (RouteHeader) request.getHeader(RouteHeader.NAME);
		if(route != null) {
			URI uri = route.getAddress().getURI();
			return findURITransport(uri, request.getContentLength().getContentLength());
		}
		URI ruri = request.getRequestURI();
		return findURITransport(ruri, request.getContentLength().getContentLength());
	}
	// https://github.com/Mobicents/sip-servlets/issues/62
	public static String findURITransport(URI uri, int messageContentLength) {
		String transport = ListeningPoint.UDP;
		if(uri instanceof SipURI) {
			SipURI sipURI = (SipURI) uri;
			if(sipURI.isSecure()) {
				transport = ListeningPoint.TLS;
			} else {
				String transportParam = sipURI.getTransportParam();

				if (transportParam != null
						&& transportParam.equalsIgnoreCase(ListeningPoint.TLS)) {
					transport = ListeningPoint.TLS;
				} // https://github.com/Mobicents/sip-servlets/issues/62
				else if (transportParam != null
						&& transportParam.equalsIgnoreCase(ListeningPointExt.WS)) {
					transport = ListeningPointExt.WS;
				} else if (transportParam != null
						&& transportParam.equalsIgnoreCase(ListeningPointExt.WSS)) {
					transport = ListeningPointExt.WSS;
				}
				// moved TCP at the bottom to avoid having TCP being chosen because of message Content Length being too big for WebRTC
				//Fix by Filip Olsson for Issue 112
				else if ((transportParam != null
						&& transportParam.equalsIgnoreCase(ListeningPoint.TCP)) || 
						messageContentLength > 4096) {
					transport = ListeningPoint.TCP;
				} 
				
			}
		}
		return transport;
	}
	
	public static boolean checkScheme(String address) {
		String tmpAddress = address;
		for(String scheme:ALLOWED_ADDRESS_SCHEMES) {
			int start = tmpAddress.indexOf("<");
			if(start >= 0) {
				int end = tmpAddress.indexOf(">");
				tmpAddress = tmpAddress.substring(start + 1, end);
			}
				
			if(scheme.equalsIgnoreCase(tmpAddress.substring(0, scheme.length())))
				return true;
		}
		return false;
	}

	public static void terminateTransaction(Transaction transaction) {
		// Issue 2130 (http://code.google.com/p/mobicents/issues/detail?id=2130) : Memory leak in Sip stack when INFO message is used 
		// fail before the ctx is created to avoid mem leaks
		if(transaction != null && transaction instanceof SIPClientTransaction) {
			if(logger.isDebugEnabled()) {
				logger.debug("terminating transaction " + transaction + " with transaction id "+ transaction.getBranchId());
			}
			try {
				transaction.terminate();
			} catch (ObjectInUseException e) {
				logger.error("Couldn't terminate the transaction " + transaction + " with transaction id "+ transaction.getBranchId());
			}
		}
	}
	
	public static void setTransactionTimers(TransactionExt transaction, SipApplicationDispatcher sipApplicationDispatcher) {
		transaction.setRetransmitTimer(sipApplicationDispatcher.getBaseTimerInterval());
	    ((TransactionExt)transaction).setTimerT2(sipApplicationDispatcher.getT2Interval());
	    ((TransactionExt)transaction).setTimerT4(sipApplicationDispatcher.getT4Interval());
	    ((TransactionExt)transaction).setTimerD(sipApplicationDispatcher.getTimerDInterval());
	}
	
	public static void optimizeRouteHeaderAddressForInternalRoutingrequest(SipConnector sipConnector, Request request, MobicentsSipSession session,  SipFactoryImpl sipFactoryImpl, String transport) {
		RouteHeader rh = (RouteHeader) request.getHeader(RouteHeader.NAME);
		javax.sip.address.URI uri = null;
		if(rh != null) {
			uri = rh.getAddress().getURI();
		} else {
			uri = request.getRequestURI();
		}
		if(uri.isSipURI()) {
			javax.sip.address.SipURI sipUri = (javax.sip.address.SipURI) uri;
			optimizeUriForInternalRoutingRequest(sipConnector, sipUri, session, sipFactoryImpl, transport);
		}

	}

	public static void optimizeUriForInternalRoutingRequest(SipConnector sipConnector, javax.sip.address.SipURI sipUri, MobicentsSipSession session,  SipFactoryImpl sipFactoryImpl, String transport) {
		SipNetworkInterfaceManager sipNetworkInterfaceManager = sipFactoryImpl.getSipNetworkInterfaceManager();

		try {
			boolean isExternal = sipFactoryImpl.getSipApplicationDispatcher().isExternal(sipUri.getHost(), sipUri.getPort(), transport);
			if(!isExternal) {
				if(logger.isDebugEnabled()) {
					logger.debug("The request is going internally due to sipUri = " + sipUri);
				}
				// http://code.google.com/p/sipservlets/issues/detail?id=92
				if (!sipConnector
						.isReplaceStaticServerAddressForInternalRoutingRequest()) {
					// config that turns off optimization if uri is the static
					// server address, this allows the static server address to
					// be a hop in the request outgoing path
					if (sipConnector.getStaticServerAddress() != null
							&& (sipConnector.getStaticServerAddress().equals(
									sipUri.getHost()) || sipConnector
									.getStaticServerAddress().equals(
											sipUri.getHost() + ":"
													+ sipUri.getPort()))) {
						if(logger.isDebugEnabled()) {
							logger.debug("Avoiding URI optimization due to connector configuration and URI points to the static server address.");
						}
						return;
					}
				}	
				MobicentsExtendedListeningPoint lp = null;
				if(session.getOutboundInterface() != null) {
					javax.sip.address.SipURI outboundInterfaceURI = (javax.sip.address.SipURI) SipFactoryImpl.addressFactory.createURI(session.getOutboundInterface());
					lp = sipNetworkInterfaceManager.findMatchingListeningPoint(outboundInterfaceURI, false);
				} else {
					lp = sipNetworkInterfaceManager.findMatchingListeningPoint(transport, false);
				}
				sipUri.setHost(lp.getHost(false));
				sipUri.setPort(lp.getPort());
				sipUri.setTransportParam(lp.getTransport());
			}
		} catch (ParseException e) {
			logger.error("AR optimization error", e);
		}
	}

	public static void optimizeViaHeaderAddressForStaticAddress(SipConnector sipConnector, Request request, SipFactoryImpl sipFactoryImpl, String transport) throws ParseException, InvalidArgumentException {
		javax.sip.address.URI uri = request.getRequestURI();

		ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);

		RouteHeader route = (RouteHeader) request.getHeader(RouteHeader.NAME);
		if(route != null) {
			uri = route.getAddress().getURI();
		}
		if(uri.isSipURI()) {
			javax.sip.address.SipURI sipUri = (javax.sip.address.SipURI) uri;
			String host = sipUri.getHost();
			int port = sipUri.getPort();
			if(sipFactoryImpl.getSipApplicationDispatcher().isExternal(host, port, transport)) {
				viaHeader.setHost(sipConnector.getStaticServerAddress());
				viaHeader.setPort(sipConnector.getStaticServerPort());
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy