/* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.impl.protocol.sip; import java.net.*; import java.text.*; import java.util.*; import javax.sip.*; import javax.sip.address.*; import javax.sip.header.*; import javax.sip.message.*; import org.osgi.framework.*; import net.java.sip.communicator.impl.protocol.sip.security.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import gov.nist.javax.sip.header.*; import gov.nist.javax.sip.address.*; import gov.nist.javax.sip.message.*; /** * A SIP implementation of the Protocol Provider Service. * * @author Emil Ivov * @author Lubomir Marinov * @author Alan Kelly */ public class ProtocolProviderServiceSipImpl extends AbstractProtocolProviderService implements SipListener, RegistrationStateChangeListener { private static final Logger logger = Logger.getLogger(ProtocolProviderServiceSipImpl.class); /** * The identifier of the account that this provider represents. */ private AccountID accountID = null; /** * We use this to lock access to initialization. */ private final Object initializationLock = new Object(); /** * indicates whether or not the provider is initialized and ready for use. */ private boolean isInitialized = false; /** * A list of all events registered for this provider. */ private final List<String> registeredEvents = new ArrayList<String>(); /** * The AddressFactory used to create URLs ans Address objects. */ private AddressFactory addressFactory; /** * The HeaderFactory used to create SIP message headers. */ private HeaderFactory headerFactory; /** * The Message Factory used to create SIP messages. */ private MessageFactory messageFactory; /** * The class in charge of event dispatching and managing common JAIN-SIP * resources */ private static SipStackSharing sipStackSharing = null; /** * A table mapping SIP methods to method processors (every processor must * implement the SipListener interface). Whenever a new message arrives we * extract its method and hand it to the processor instance registered */ private final Hashtable<String, List<MethodProcessor>> methodProcessors = new Hashtable<String, List<MethodProcessor>>(); /** * A random generator we use to generate tags. */ private static Random localTagGenerator = new Random(); /** * The name of the property under which the user may specify the number of * the port where they would prefer us to bind our sip socket. */ public static final String PREFERRED_SIP_PORT = "net.java.sip.communicator.service.protocol.sip.PREFERRED_SIP_PORT"; /** * The name of the property under which the user may specify the number of * seconds that registrations take to expire. */ private static final String REGISTRATION_EXPIRATION = "net.java.sip.communicator.impl.protocol.sip.REGISTRATION_EXPIRATION"; /** * The name of the property under which the user may specify whether or not * REGISTER requests should be using a route header. Default is false */ private static final String REGISTERS_USE_ROUTE = "net.java.sip.communicator.impl.protocol.sip.REGISTERS_USE_ROUTE"; /** * The name of the property under which the user may specify a transport * to use for destinations whose prefererred transport is unknown. */ private static final String DEFAULT_TRANSPORT = "net.java.sip.communicator.impl.protocol.sip.DEFAULT_TRANSPORT"; /** * Default number of times that our requests can be forwarded. */ private static final int MAX_FORWARDS = 70; /** * Keep-alive method can be - register,options or udp */ public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD"; /** * The interval for keep-alive */ public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL"; /** * The default maxForwards header that we use in our requests. */ private MaxForwardsHeader maxForwardsHeader = null; /** * The header that we use to identify ourselves. */ private UserAgentHeader userAgentHeader = null; /** * The name that we want to send others when calling or chatting with them. */ private String ourDisplayName = null; /** * Our current connection with the registrar. */ private SipRegistrarConnection sipRegistrarConnection = null; /** * The SipSecurityManager instance that would be taking care of our * authentications. */ private SipSecurityManager sipSecurityManager = null; /** * The string representing our outbound proxy if we have one (remains null * if we are not using a proxy). */ private String outboundProxyString = null; /** * The address and port of an outbound proxy if we have one (remains null * if we are not using a proxy). */ private InetSocketAddress outboundProxySocketAddress = null; /** * The transport used by our outbound proxy (remains null * if we are not using a proxy). */ private String outboundProxyTransport = null; /** * The logo corresponding to the jabber protocol. */ private ProtocolIconSipImpl protocolIcon; /** * The presence status set supported by this provider */ private SipStatusEnum sipStatusEnum; /** * Returns the AccountID that uniquely identifies the account represented by * this instance of the ProtocolProviderService. * @return the id of the account represented by this provider. */ public AccountID getAccountID() { return accountID; } /** * Returns the state of the registration of this protocol provider with the * corresponding registration service. * @return ProviderRegistrationState */ public RegistrationState getRegistrationState() { if(this.sipRegistrarConnection == null ) { return RegistrationState.UNREGISTERED; } return sipRegistrarConnection.getRegistrationState(); } /** * Returns the short name of the protocol that the implementation of this * provider is based upon (like SIP, Jabber, ICQ/AIM, or others for * example). If the name of the protocol has been enumerated in * ProtocolNames then the value returned by this method must be the same as * the one in ProtocolNames. * @return a String containing the short name of the protocol this service * is implementing (most often that would be a name in ProtocolNames). */ public String getProtocolName() { return ProtocolNames.SIP; } /** * Register a new event taken in account by this provider. This is usefull * to generate the Allow-Events header of the OPTIONS responses and to * generate 489 responses. * * @param event The event to register */ public void registerEvent(String event) { synchronized (this.registeredEvents) { if (!this.registeredEvents.contains(event)) { this.registeredEvents.add(event); } } } /** * Returns the list of all the registered events for this provider. * * @return The list of all the registered events */ public List<String> getKnownEventsList() { return this.registeredEvents; } /** * Starts the registration process. Connection details such as * registration server, user name/number are provided through the * configuration service through implementation specific properties. * * @param authority the security authority that will be used for resolving * any security challenges that may be returned during the * registration or at any moment while wer're registered. * * @throws OperationFailedException with the corresponding code it the * registration fails for some reason (e.g. a networking error or an * implementation problem). */ public void register(SecurityAuthority authority) throws OperationFailedException { if(!isInitialized) { throw new OperationFailedException( "Provided must be initialized before being able to register." , OperationFailedException.GENERAL_ERROR); } if (isRegistered()) { return; } sipStackSharing.addSipListener(this); // be warned when we will unregister, so that we can // then remove us as SipListener this.addRegistrationStateChangeListener(this); // Enable the user name modification. Setting this property to true we'll // allow the user to change the user name stored in the given authority. authority.setUserNameEditable(true); //init the security manager before doing the actual registration to //avoid being asked for credentials before being ready to provide them sipSecurityManager.setSecurityAuthority(authority); // We check here if the sipRegistrarConnection is initialized. This is // needed in case that in the initialization process we had no internet // connection and resolving the registrar failed. if (sipRegistrarConnection == null) initRegistrarConnection((SipAccountID) accountID); // The same here, we check if the outbound proxy is initialized in case // through the initialization process there was no internet connection. if (outboundProxySocketAddress == null) initOutboundProxy((SipAccountID)accountID); //connect to the Registrar. if (sipRegistrarConnection != null) sipRegistrarConnection.register(); } /** * Ends the registration of this protocol provider with the current * registration service. * * @throws OperationFailedException with the corresponding code it the * registration fails for some reason (e.g. a networking error or an * implementation problem). */ public void unregister() throws OperationFailedException { if(getRegistrationState().equals(RegistrationState.UNREGISTERED) || getRegistrationState().equals(RegistrationState.UNREGISTERING)) { return; } sipRegistrarConnection.unregister(); sipSecurityManager.setSecurityAuthority(null); } /** * Initializes the service implementation, and puts it in a state where it * could interoperate with other services. * * @param sipAddress the account id/uin/screenname of the account that we're * about to create * @param accountID the identifier of the account that this protocol * provider represents. * @param isInstall indicates if this initialization is made due to a new * account installation or just an existing account loading * * @throws OperationFailedException with code INTERNAL_ERROR if we fail * initializing the SIP Stack. * @throws java.lang.IllegalArgumentException if one or more of the account * properties have invalid values. * * @see net.java.sip.communicator.service.protocol.AccountID */ protected void initialize(String sipAddress, SipAccountID accountID) throws OperationFailedException, IllegalArgumentException { synchronized (initializationLock) { this.accountID = accountID; String protocolIconPath = accountID .getAccountPropertyString(ProtocolProviderFactory.PROTOCOL_ICON_PATH); if (protocolIconPath == null) protocolIconPath = "resources/images/protocol/sip"; this.protocolIcon = new ProtocolIconSipImpl(protocolIconPath); this.sipStatusEnum = new SipStatusEnum(protocolIconPath); //init the proxy initOutboundProxy(accountID); //init proxy port int preferredSipPort = ListeningPoint.PORT_5060; String proxyPortStr = SipActivator.getConfigurationService(). getString(PREFERRED_SIP_PORT); if (proxyPortStr != null && proxyPortStr.length() > 0) { try { preferredSipPort = Integer.parseInt(proxyPortStr); } catch (NumberFormatException ex) { logger.error( proxyPortStr + " is not a valid port value. Expected an integer" , ex); } if (preferredSipPort > NetworkUtils.MAX_PORT_NUMBER) { logger.error(preferredSipPort + " is larger than " + NetworkUtils.MAX_PORT_NUMBER + " and does not " + "therefore represent a valid port nubmer."); } } if(sipStackSharing == null) sipStackSharing = new SipStackSharing(); // get the presence options boolean enablePresence = accountID.getAccountPropertyBoolean( ProtocolProviderFactory.IS_PRESENCE_ENABLED, true); boolean forceP2P = accountID.getAccountPropertyBoolean(ProtocolProviderFactory.FORCE_P2P_MODE, true); int pollingValue = accountID.getAccountPropertyInt( ProtocolProviderFactory.POLLING_PERIOD, 30); int subscriptionExpiration = accountID.getAccountPropertyInt( ProtocolProviderFactory.SUBSCRIPTION_EXPIRATION, 3600); //create SIP factories. headerFactory = new HeaderFactoryImpl(); addressFactory = new AddressFactoryImpl(); //create a connection with the registrar initRegistrarConnection(accountID); //init our call processor OperationSetAdvancedTelephony opSetAdvancedTelephony = new OperationSetBasicTelephonySipImpl(this); this.supportedOperationSets.put( OperationSetBasicTelephony.class.getName() , opSetAdvancedTelephony); this.supportedOperationSets.put( OperationSetAdvancedTelephony.class.getName() , opSetAdvancedTelephony); // init ZRTP (OperationSetBasicTelephonySipImpl implements // OperationSetSecureTelephony) this.supportedOperationSets.put( OperationSetSecureTelephony.class.getName() , opSetAdvancedTelephony); //init presence op set. OperationSetPersistentPresence opSetPersPresence = new OperationSetPresenceSipImpl(this, enablePresence, forceP2P, pollingValue, subscriptionExpiration); this.supportedOperationSets.put( OperationSetPersistentPresence.class.getName() , opSetPersPresence); //also register with standard presence this.supportedOperationSets.put( OperationSetPresence.class.getName() , opSetPersPresence); // init instant messaging OperationSetBasicInstantMessagingSipImpl opSetBasicIM = new OperationSetBasicInstantMessagingSipImpl(this); this.supportedOperationSets.put( OperationSetBasicInstantMessaging.class.getName(), opSetBasicIM); // init typing notifications OperationSetTypingNotificationsSipImpl opSetTyping = new OperationSetTypingNotificationsSipImpl(this, opSetBasicIM); this.supportedOperationSets.put( OperationSetTypingNotifications.class.getName(), opSetTyping); // OperationSetVideoTelephony supportedOperationSets.put(OperationSetVideoTelephony.class .getName(), new OperationSetVideoTelephonySipImpl()); // init DTMF (from JM Heitz) OperationSetDTMF opSetDTMF = new OperationSetDTMFSipImpl(this); this.supportedOperationSets.put( OperationSetDTMF.class.getName(), opSetDTMF); //initialize our OPTIONS handler new ClientCapabilities(this); //initialize our display name ourDisplayName = accountID .getAccountPropertyString(ProtocolProviderFactory.DISPLAY_NAME); if(ourDisplayName == null || ourDisplayName.trim().length() == 0) { ourDisplayName = accountID.getUserID(); } //init the security manager this.sipSecurityManager = new SipSecurityManager(accountID); sipSecurityManager.setHeaderFactory(headerFactory); isInitialized = true; } } /** * Never called. * @see net.java.sip.communicator.impl.protocol.sip.PersistentService#processIOException(IOExceptionEvent) */ public void processIOException(IOExceptionEvent exceptionEvent) {} /** * Processes a Response received on a SipProvider upon which this * SipListener is registered. * <p> * * @param responseEvent the responseEvent fired from the SipProvider to the * SipListener representing a Response received from the network. */ public void processResponse(ResponseEvent responseEvent) { logger.debug("received response=\n" + responseEvent.getResponse()); ClientTransaction clientTransaction = responseEvent .getClientTransaction(); if (clientTransaction == null) { logger.debug("ignoring a transactionless response"); return; } Response response = responseEvent.getResponse(); String method = ( (CSeqHeader) response.getHeader(CSeqHeader.NAME)) .getMethod(); //find the object that is supposed to take care of responses with the //corresponding method List<MethodProcessor> processors = methodProcessors.get(method); if (processors != null) { logger.debug("Found " + processors.size() + " processor(s) for method " + method); for (MethodProcessor processor : processors) { if (processor.processResponse(responseEvent)) { break; } } } } /** * Processes a retransmit or expiration Timeout of an underlying * {@link Transaction} handled by this SipListener. This Event notifies the * application that a retransmission or transaction Timer expired in the * SipProvider's transaction state machine. The TimeoutEvent encapsulates * the specific timeout type and the transaction identifier either client or * server upon which the timeout occurred. The type of Timeout can by * determined by: * <code>timeoutType = timeoutEvent.getTimeout().getValue();</code> * * @param timeoutEvent - * the timeoutEvent received indicating either the message * retransmit or transaction timed out. */ public void processTimeout(TimeoutEvent timeoutEvent) { Transaction transaction; if(timeoutEvent.isServerTransaction()) transaction = timeoutEvent.getServerTransaction(); else transaction = timeoutEvent.getClientTransaction(); if (transaction == null) { logger.debug("ignoring a transactionless timeout event"); return; } Request request = transaction.getRequest(); logger.debug("received timeout for req=" + request); //find the object that is supposed to take care of responses with the //corresponding method String method = request.getMethod(); List<MethodProcessor> processors = methodProcessors.get(method); if (processors != null) { logger.debug("Found " + processors.size() + " processor(s) for method " + method); for (MethodProcessor processor : processors) { if (processor.processTimeout(timeoutEvent)) { break; } } } } /** * Process an asynchronously reported TransactionTerminatedEvent. * When a transaction transitions to the Terminated state, the stack * keeps no further records of the transaction. This notification can be used by * applications to clean up any auxiliary data that is being maintained * for the given transaction. * * @param transactionTerminatedEvent -- an event that indicates that the * transaction has transitioned into the terminated state. * @since v1.2 */ public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) { Transaction transaction; if(transactionTerminatedEvent.isServerTransaction()) transaction = transactionTerminatedEvent.getServerTransaction(); else transaction = transactionTerminatedEvent.getClientTransaction(); if (transaction == null) { logger.debug( "ignoring a transactionless transaction terminated event"); return; } Request request = transaction.getRequest(); logger.debug("Transaction terminated for req=" + request); //find the object that is supposed to take care of responses with the //corresponding method String method = request.getMethod(); List<MethodProcessor> processors = methodProcessors.get(method); if (processors != null) { logger.debug("Found " + processors.size() + " processor(s) for method " + method); for (MethodProcessor processor : processors) { if (processor.processTransactionTerminated( transactionTerminatedEvent)) { break; } } } } /** * Process an asynchronously reported DialogTerminatedEvent. * When a dialog transitions to the Terminated state, the stack * keeps no further records of the dialog. This notification can be used by * applications to clean up any auxiliary data that is being maintained * for the given dialog. * * @param dialogTerminatedEvent -- an event that indicates that the * dialog has transitioned into the terminated state. * @since v1.2 */ public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { logger.debug("Dialog terminated for req=" + dialogTerminatedEvent.getDialog()); } /** * Processes a Request received on a SipProvider upon which this SipListener * is registered. * <p> * @param requestEvent requestEvent fired from the SipProvider to the * SipListener representing a Request received from the network. */ public void processRequest(RequestEvent requestEvent) { logger.debug("received request=\n" + requestEvent.getRequest()); Request request = requestEvent.getRequest(); // test if an Event header is present and known EventHeader eventHeader = (EventHeader) request.getHeader(EventHeader.NAME); if (eventHeader != null) { boolean eventKnown; synchronized (this.registeredEvents) { eventKnown = this.registeredEvents.contains( eventHeader.getEventType()); } if (!eventKnown) { // send a 489 / Bad Event response ServerTransaction serverTransaction = requestEvent .getServerTransaction(); SipProvider jainSipProvider = (SipProvider) requestEvent.getSource(); if (serverTransaction == null) { try { serverTransaction = jainSipProvider .getNewServerTransaction(request); } catch (TransactionAlreadyExistsException ex) { //let's not scare the user and only log a message logger.error("Failed to create a new server" + "transaction for an incoming request\n" + "(Next message contains the request)" , ex); return; } catch (TransactionUnavailableException ex) { //let's not scare the user and only log a message logger.error("Failed to create a new server" + "transaction for an incoming request\n" + "(Next message contains the request)" , ex); return; } } Response response = null; try { response = this.getMessageFactory().createResponse( Response.BAD_EVENT, request); } catch (ParseException e) { logger.error("failed to create the 489 response", e); return; } try { serverTransaction.sendResponse(response); } catch (SipException e) { logger.error("failed to send the response", e); } catch (InvalidArgumentException e) { // should not happen logger.error("invalid argument provided while trying" + " to send the response", e); } } } String method = request.getMethod(); //find the object that is supposed to take care of responses with the //corresponding method List<MethodProcessor> processors = methodProcessors.get(method); if (processors != null) { logger.debug("Found " + processors.size() + " processor(s) for method " + method); for (MethodProcessor processor : processors) { if (processor.processRequest(requestEvent)) { break; } } } } /** * Makes the service implementation close all open sockets and release * any resources that it might have taken and prepare for shutdown/garbage * collection. */ public void shutdown() { if(!isInitialized) { return; } // launch the shutdown process in a thread to free the GUI as soon // as possible even if the SIP unregistration process may take time // especially for ending SIMPLE Thread t = new Thread(new ShutdownThread()); t.setDaemon(false); t.run(); } protected class ShutdownThread implements Runnable { public void run() { logger.trace("Killing the SIP Protocol Provider."); //kill all active calls OperationSetBasicTelephonySipImpl telephony = (OperationSetBasicTelephonySipImpl)getOperationSet( OperationSetBasicTelephony.class); telephony.shutdown(); if(isRegistered()) { try { //create a listener that would notify us when //un-registration has completed. ShutdownUnregistrationBlockListener listener = new ShutdownUnregistrationBlockListener(); addRegistrationStateChangeListener(listener); //do the un-registration unregister(); //leave ourselves time to complete un-registration (may include //2 REGISTER requests in case notification is needed.) listener.waitForEvent(5000); } catch (OperationFailedException ex) { //we're shutting down so we need to silence the exception here logger.error( "Failed to properly unregister before shutting down. " + getAccountID() , ex); } } headerFactory = null; messageFactory = null; addressFactory = null; sipSecurityManager = null; methodProcessors.clear(); isInitialized = false; } } /** * Generate a tag for a FROM header or TO header. Just return a random 4 * digit integer (should be enough to avoid any clashes!) Tags only need to * be unique within a call. * * @return a string that can be used as a tag parameter. * * synchronized: needed for access to 'rand', else risk to generate same tag * twice */ public static synchronized String generateLocalTag() { return Integer.toHexString(localTagGenerator.nextInt()); } /** * Initializes and returns an ArrayList with a single ViaHeader * containing a localhost address usable with the specified * s<tt>destination</tt>. This ArrayList may be used when sending * requests to that destination. * <p> * @param intendedDestination The address of the destination that the * request using the via headers will be sent to. * * @return ViaHeader-s list to be used when sending requests. * @throws OperationFailedException code INTERNAL_ERROR if a ParseException * occurs while initializing the array list. * */ public ArrayList<ViaHeader> getLocalViaHeaders(Address intendedDestination) throws OperationFailedException { return getLocalViaHeaders((SipURI)intendedDestination.getURI()); } /** * Initializes and returns an ArrayList with a single ViaHeader * containing a localhost address usable with the specified * s<tt>destination</tt>. This ArrayList may be used when sending * requests to that destination. * <p> * @param intendedDestination The address of the destination that the * request using the via headers will be sent to. * * @return ViaHeader-s list to be used when sending requests. * @throws OperationFailedException code INTERNAL_ERROR if a ParseException * occurs while initializing the array list. * */ public ArrayList<ViaHeader> getLocalViaHeaders(SipURI intendedDestination) throws OperationFailedException { ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); ListeningPoint srcListeningPoint = getListeningPoint(intendedDestination.getTransportParam()); try { InetAddress localAddress = SipActivator .getNetworkAddressManagerService().getLocalHost( getIntendedDestination(intendedDestination)); ViaHeader viaHeader = headerFactory.createViaHeader( localAddress.getHostAddress() , srcListeningPoint.getPort() , srcListeningPoint.getTransport() , null ); viaHeaders.add(viaHeader); logger.debug("generated via headers:" + viaHeader); return viaHeaders; } catch (ParseException ex) { logger.error( "A ParseException occurred while creating Via Headers!", ex); throw new OperationFailedException( "A ParseException occurred while creating Via Headers!" ,OperationFailedException.INTERNAL_ERROR ,ex); } catch (InvalidArgumentException ex) { logger.error( "Unable to create a via header for port " + sipStackSharing.getLP(ListeningPoint.UDP).getPort(), ex); throw new OperationFailedException( "Unable to create a via header for port " + sipStackSharing.getLP(ListeningPoint.UDP).getPort() ,OperationFailedException.INTERNAL_ERROR ,ex); } } /** * Initializes and returns this provider's default maxForwardsHeader field * using the value specified by MAX_FORWARDS. * * @return an instance of a MaxForwardsHeader that can be used when * sending requests * * @throws OperationFailedException with code INTERNAL_ERROR if MAX_FORWARDS * has an invalid value. */ public MaxForwardsHeader getMaxForwardsHeader() throws OperationFailedException { if (maxForwardsHeader == null) { try { maxForwardsHeader = headerFactory.createMaxForwardsHeader( MAX_FORWARDS); logger.debug("generated max forwards: " + maxForwardsHeader.toString()); } catch (InvalidArgumentException ex) { throw new OperationFailedException( "A problem occurred while creating MaxForwardsHeader" , OperationFailedException.INTERNAL_ERROR , ex); } } return maxForwardsHeader; } /** * Returns a Contact header containing a sip URI based on a localhost * address and therefore usable in REGISTER requests only. * * @param intendedDestination the destination that we plan to be sending * this contact header to. * * @return a Contact header based upon a local inet address. * @throws OperationFailedException if we fail constructing the contact * header. */ public ContactHeader getContactHeader(Address intendedDestination) { return getContactHeader((SipURI)intendedDestination.getURI()); } /** * Returns a Contact header containing a sip URI based on a localhost * address and therefore usable in REGISTER requests only. * * @param intendedDestination the destination that we plan to be sending * this contact header to. * * @return a Contact header based upon a local inet address. * @throws OperationFailedException if we fail constructing the contact * header. */ public ContactHeader getContactHeader(SipURI intendedDestination) { ContactHeader registrationContactHeader = null; ListeningPoint srcListeningPoint = getListeningPoint(intendedDestination); InetAddress targetAddress = getIntendedDestination(intendedDestination); try { //find the address to use with the target InetAddress localAddress = SipActivator .getNetworkAddressManagerService().getLocalHost(targetAddress); SipURI contactURI = addressFactory.createSipURI( getAccountID().getUserID() , localAddress.getHostAddress() ); contactURI.setTransportParam(srcListeningPoint.getTransport()); contactURI.setPort(srcListeningPoint.getPort()); // set a custom param to ease incoming requests dispatching in case // we have several registrar accounts with the same username String paramValue = getContactAddressCustomParamValue(); if (paramValue != null) { contactURI.setParameter( SipStackSharing.CONTACT_ADDRESS_CUSTOM_PARAM_NAME, paramValue ); } Address contactAddress = addressFactory.createAddress( contactURI ); if (ourDisplayName != null) { contactAddress.setDisplayName(ourDisplayName); } registrationContactHeader = headerFactory.createContactHeader( contactAddress); logger.debug("generated contactHeader:" + registrationContactHeader); } catch (ParseException ex) { logger.error( "A ParseException occurred while creating From Header!", ex); throw new IllegalArgumentException( "A ParseException occurred while creating From Header!" , ex); } return registrationContactHeader; } /** * Returns null for a registraless account, a value for the contact address * custom parameter otherwise. This will help the dispatching of incoming * requests between accounts with the same username. For address-of-record * user@example.com, the returned value woud be example_com. * * @return null for a registraless account, a value for the * "registering_acc" contact address parameter otherwise */ public String getContactAddressCustomParamValue() { SipRegistrarConnection src = getRegistrarConnection(); if (src != null && !src.isRegistrarless()) { // if we don't replace the dots in the hostname, we get // "476 No Server Address in Contacts Allowed" // from certain registrars (ippi.fr for instance) String hostValue = ((SipURI) src.getAddressOfRecord().getURI()) .getHost().replace('.', '_'); return hostValue; } return null; } /** * Returns the AddressFactory used to create URLs ans Address objects. * * @return the AddressFactory used to create URLs ans Address objects. */ public AddressFactory getAddressFactory() { return addressFactory; } /** * Returns the HeaderFactory used to create SIP message headers. * * @return the HeaderFactory used to create SIP message headers. */ public HeaderFactory getHeaderFactory() { return headerFactory; } /** * Returns the Message Factory used to create SIP messages. * * @return the Message Factory used to create SIP messages. */ public MessageFactory getMessageFactory() { if (messageFactory == null) { messageFactory = new SipMessageFactory(this, new MessageFactoryImpl()); } return messageFactory; } /** * Returns all running instances of ProtocolProviderServiceSipImpl * * @return all running instances of ProtocolProviderServiceSipImpl */ public static Set<ProtocolProviderServiceSipImpl> getAllInstances() { try { Set<ProtocolProviderServiceSipImpl> instances = new HashSet<ProtocolProviderServiceSipImpl>(); BundleContext context = SipActivator.getBundleContext(); ServiceReference[] references = context.getServiceReferences( ProtocolProviderService.class.getName(), null ); for(ServiceReference reference : references) { Object service = context.getService(reference); if(service instanceof ProtocolProviderServiceSipImpl) instances.add((ProtocolProviderServiceSipImpl) service); } return instances; } catch(InvalidSyntaxException ex) { logger.debug("Problem parcing an osgi expression", ex); // should never happen so crash if it ever happens throw new RuntimeException( "getServiceReferences() wasn't supposed to fail!" ); } } /** * Returns the default listening point that we use for communication over * <tt>transport</tt>. * * @param transport the transport that the returned listening point needs * to support. * * @return the default listening point that we use for communication over * <tt>transport</tt> or null if no such transport is supported. */ public ListeningPoint getListeningPoint(String transport) { if(logger.isTraceEnabled()) logger.trace("Query for a " + transport + " listening point"); if( transport == null || transport.trim().length() == 0 || ( ! transport.trim().equalsIgnoreCase(ListeningPoint.TCP) && ! transport.trim().equalsIgnoreCase(ListeningPoint.UDP) && ! transport.trim().equalsIgnoreCase(ListeningPoint.TLS))) { transport = getDefaultTransport(); } ListeningPoint lp = null; if(transport.equalsIgnoreCase(ListeningPoint.UDP)) { lp = sipStackSharing.getLP(ListeningPoint.UDP); } else if(transport.equalsIgnoreCase(ListeningPoint.TCP)) { lp = sipStackSharing.getLP(ListeningPoint.TCP); } else if(transport.equalsIgnoreCase(ListeningPoint.TLS)) { lp = sipStackSharing.getLP(ListeningPoint.TLS); } if(logger.isTraceEnabled()) { logger.trace("Returning LP " + lp + " for transport [" + transport + "]" + " and "); } return lp; } /** * Returns the default listening point that we should use to contact the * intended destination. * * @param intendedDestination the address that we will be trying to contact * through the listening point we are trying to obtain. * * @return the listening point that we should use to contact the * intended destination. */ public ListeningPoint getListeningPoint(Address intendedDestination) { return getListeningPoint((SipURI)intendedDestination.getURI()); } /** * Returns the default listening point that we should use to contact the * intended destination. * * @param intendedDestination the address that we will be trying to contact * through the listening point we are trying to obtain. * * @return the listening point that we should use to contact the * intended destination. */ public ListeningPoint getListeningPoint(SipURI intendedDestination) { String transport = intendedDestination.getTransportParam(); return getListeningPoint(transport); } /** * Returns the default jain sip provider that we use for communication over * <tt>transport</tt>. * * @param transport the transport that the returned provider needs * to support. * * @return the default jain sip provider that we use for communication over * <tt>transport</tt> or null if no such transport is supported. */ public SipProvider getJainSipProvider(String transport) { return sipStackSharing.getJainSipProvider(transport); } /** * Reurns the currently valid sip security manager that everyone should * use to authenticate SIP Requests. * @return the currently valid instace of a SipSecurityManager that everyone * sould use to authenticate SIP Requests. */ public SipSecurityManager getSipSecurityManager() { return sipSecurityManager; } /** * Initializes the SipRegistrarConnection that this class will be using. * * @param accountID the ID of the account that this registrar is associated * with. * @throws java.lang.IllegalArgumentException if one or more account * properties have invalid values. */ private void initRegistrarConnection(SipAccountID accountID) throws IllegalArgumentException { //First init the registrar address String registrarAddressStr = accountID .getAccountPropertyString(ProtocolProviderFactory.SERVER_ADDRESS); //if there is no registrar address, parse the user_id and extract it //from the domain part of the SIP URI. if (registrarAddressStr == null) { String userID = accountID .getAccountPropertyString(ProtocolProviderFactory.USER_ID); int index = userID.indexOf("@"); if ( index > -1 ) registrarAddressStr = userID.substring( index+1); } //if we still have no registrar address or if the registrar address //string is one of our local host addresses this means the users does //not want to use a registrar connection if(registrarAddressStr == null || registrarAddressStr.trim().length() == 0) { initRegistrarlessConnection(accountID); return; } //from this point on we are certain to have a registrar. InetAddress registrarAddress = null; //init registrar port int registrarPort = ListeningPoint.PORT_5060; try { // first check for srv records exists String registrarTransport = accountID .getAccountPropertyString(ProtocolProviderFactory.PREFERRED_TRANSPORT); if(registrarTransport == null) registrarTransport = getDefaultTransport(); InetSocketAddress registrarSocketAddress = resolveSipAddress( registrarAddressStr, registrarTransport); registrarAddress = registrarSocketAddress.getAddress(); registrarPort = registrarSocketAddress.getPort(); // We should set here the property to indicate that the server // address is validated. When we load stored accounts we check // this property in order to prevent checking again the server // address. And this is needed because in the case we don't have // network while loading the application we still want to have our // accounts loaded. accountID.putAccountProperty( ProtocolProviderFactory.SERVER_ADDRESS_VALIDATED, Boolean.toString(true)); } catch (UnknownHostException ex) { logger.debug(registrarAddressStr + " appears to be an either invalid" + " or inaccessible address.", ex); boolean isServerValidated = accountID.getAccountPropertyBoolean( ProtocolProviderFactory.SERVER_ADDRESS_VALIDATED, false); /* * We should check here if the server address was already validated. * When we load stored accounts we want to prevent checking again * the server address. This is needed because in the case we don't * have network while loading the application we still want to have * our accounts loaded. */ if (!isServerValidated) { throw new IllegalArgumentException( registrarAddressStr + " appears to be an either invalid" + " or inaccessible address.", ex); } } // If the registrar address is null we don't need to continue. // If we still have problems with initializing the registrar we are // telling the user. We'll enter here only if the server has been // already validated (this means that the account is already created // and we're trying to login, but we have no internet connection). if(registrarAddress == null) { fireRegistrationStateChanged( RegistrationState.UNREGISTERED, RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, "Invalid or inaccessible server address."); return; } // check if user has overridden the registrar port. registrarPort = accountID.getAccountPropertyInt( ProtocolProviderFactory.SERVER_PORT, registrarPort); if (registrarPort > NetworkUtils.MAX_PORT_NUMBER) { throw new IllegalArgumentException(registrarPort + " is larger than " + NetworkUtils.MAX_PORT_NUMBER + " and does not therefore represent a valid port nubmer."); } //registrar transport String registrarTransport = accountID .getAccountPropertyString(ProtocolProviderFactory.PREFERRED_TRANSPORT); if(registrarTransport != null && registrarTransport.length() > 0) { if( ! registrarTransport.equals(ListeningPoint.UDP) && !registrarTransport.equals(ListeningPoint.TCP) && !registrarTransport.equals(ListeningPoint.TLS)) { throw new IllegalArgumentException(registrarTransport + " is not a valid transport protocol. Transport must be " +"left blanc or set to TCP, UDP or TLS."); } } else { registrarTransport = ListeningPoint.UDP; } //init expiration timeout int expires = SipActivator.getConfigurationService().getInt( REGISTRATION_EXPIRATION, SipRegistrarConnection.DEFAULT_REGISTRATION_EXPIRATION); //Initialize our connection with the registrar try { this.sipRegistrarConnection = new SipRegistrarConnection( registrarAddress , registrarPort , registrarTransport , expires , this); // determine whether we should be using route headers or not boolean useRoute = accountID.getAccountPropertyBoolean(REGISTERS_USE_ROUTE, false); this.sipRegistrarConnection.setRouteHeaderEnabled(useRoute); } catch (ParseException ex) { //this really shouldn't happen as we're using InetAddress-es logger.error("Failed to create a registrar connection with " +registrarAddress.getHostAddress() , ex); throw new IllegalArgumentException( "Failed to create a registrar connection with " + registrarAddress.getHostAddress() + ": " + ex.getMessage()); } } /** * Initializes the SipRegistrarConnection that this class will be using. * * @param accountID the ID of the account that this registrar is associated * with. * @throws java.lang.IllegalArgumentException if one or more account * properties have invalid values. */ private void initRegistrarlessConnection(SipAccountID accountID) throws IllegalArgumentException { //registrar transport String registrarTransport = accountID .getAccountPropertyString(ProtocolProviderFactory.PREFERRED_TRANSPORT); if(registrarTransport != null && registrarTransport.length() > 0) { if( ! registrarTransport.equals(ListeningPoint.UDP) && !registrarTransport.equals(ListeningPoint.TCP) && !registrarTransport.equals(ListeningPoint.TLS)) { throw new IllegalArgumentException(registrarTransport + " is not a valid transport protocol. Transport must be " +"left blanc or set to TCP, UDP or TLS."); } } else { registrarTransport = ListeningPoint.UDP; } //Initialize our connection with the registrar this.sipRegistrarConnection = new SipRegistrarlessConnection(this, registrarTransport); } /** * Returns the SIP address of record (Display Name <user@server.net>) that * this account is created for. The method takes into account whether or * not we are running in Registar or "No Registar" mode and either returns * the AOR we are using to register or an address constructed using the * local address * . * @return our Address Of Record that we should use in From headers. */ public Address getOurSipAddress(Address intendedDestination) { return getOurSipAddress((SipURI)intendedDestination.getURI()); } /** * Returns the SIP address of record (Display Name <user@server.net>) that * this account is created for. The method takes into account whether or * not we are running in Registar or "No Registar" mode and either returns * the AOR we are using to register or an address constructed using the * local address * . * @return our Address Of Record that we should use in From headers. */ public Address getOurSipAddress(SipURI intendedDestination) { SipRegistrarConnection src = getRegistrarConnection(); if( src != null & !src.isRegistrarless() ) return src.getAddressOfRecord(); //we are apparently running in "No Registrar" mode so let's create an //address by ourselves. InetAddress destinationAddr = getIntendedDestination(intendedDestination); InetAddress localHost = SipActivator.getNetworkAddressManagerService() .getLocalHost(destinationAddr); String userID = getAccountID().getUserID(); try { SipURI ourSipURI = getAddressFactory() .createSipURI(userID, localHost.getHostAddress()); ListeningPoint lp = getListeningPoint(intendedDestination); ourSipURI.setTransportParam(lp.getTransport()); ourSipURI.setPort(lp.getPort()); Address ourSipAddress = getAddressFactory() .createAddress(getOurDisplayName(), ourSipURI); ourSipAddress.setDisplayName(getOurDisplayName()); return ourSipAddress; } catch (ParseException exc) { // this should never happen since we are using InetAddresses // everywhere so parsing could hardly go wrong. throw new IllegalArgumentException( "Failed to create our SIP AOR address" , exc); } } /** * In case we are using an outbound proxy this method returns * a suitable string for use with Router. * The method returns <tt>null</tt> otherwise. * * @return the string of our outbound proxy if we are using one and * <tt>null</tt> otherwise. */ public String getOutboundProxyString() { return this.outboundProxyString; } /** * In case we are using an outbound proxy this method returns its address. * The method returns <tt>null</tt> otherwise. * * @return the address of our outbound proxy if we are using one and * <tt>null</tt> otherwise. */ public InetSocketAddress getOutboundProxy() { return this.outboundProxySocketAddress; } /** * In case we are using an outbound proxy this method returns the transport * we are using to connect to it. The method returns <tt>null</tt> * otherwise. * * @return the transport used to connect to our outbound proxy if we are * using one and <tt>null</tt> otherwise. */ public String getOutboundProxyTransport() { return this.outboundProxyTransport; } /** * Extracts all properties concerning the usage of an outbound proxy for * this account. * @param accountID the account whose outbound proxy we are currently * initializing. */ private void initOutboundProxy(SipAccountID accountID) { //First init the proxy address String proxyAddressStr = accountID .getAccountPropertyString(ProtocolProviderFactory.PROXY_ADDRESS); if(proxyAddressStr == null || proxyAddressStr.trim().length() == 0) return; InetAddress proxyAddress = null; //init proxy port int proxyPort = ListeningPoint.PORT_5060; try { // first check for srv records exists String proxyTransport = accountID .getAccountPropertyString(ProtocolProviderFactory.PREFERRED_TRANSPORT); if(proxyTransport == null) proxyTransport = getDefaultTransport(); InetSocketAddress proxySocketAddress = resolveSipAddress( proxyAddressStr, proxyTransport); proxyAddress = proxySocketAddress.getAddress(); proxyPort = proxySocketAddress.getPort(); proxyAddressStr = proxyAddress.getHostName(); logger.trace("Setting proxy address = " + proxyAddressStr); // We should set here the property to indicate that the proxy // address is validated. When we load stored accounts we check // this property in order to prevent checking again the proxy // address. this is needed because in the case we don't have // network while loading the application we still want to have // our accounts loaded. accountID.putAccountProperty( ProtocolProviderFactory.PROXY_ADDRESS_VALIDATED, Boolean.toString(true)); } catch (UnknownHostException ex) { logger.error(proxyAddressStr + " appears to be an either invalid" + " or inaccessible address.", ex); boolean isProxyValidated = accountID.getAccountPropertyBoolean( ProtocolProviderFactory.PROXY_ADDRESS_VALIDATED, false); // We should check here if the proxy address was already validated. // When we load stored accounts we want to prevent checking again the // proxy address. This is needed because in the case we don't have // network while loading the application we still want to have our // accounts loaded. if (!isProxyValidated) { throw new IllegalArgumentException( proxyAddressStr + " appears to be an either invalid or" + " inaccessible address.", ex); } } // Return if no proxy is specified or if the proxyAddress is null. if(proxyAddressStr == null || proxyAddressStr.length() == 0 || proxyAddress == null) { return; } //check if user has overridden proxy port. proxyPort = accountID.getAccountPropertyInt(ProtocolProviderFactory.PROXY_PORT, proxyPort); if (proxyPort > NetworkUtils.MAX_PORT_NUMBER) { throw new IllegalArgumentException(proxyPort + " is larger than " + NetworkUtils.MAX_PORT_NUMBER + " and does not therefore represent a valid port nubmer."); } //proxy transport String proxyTransport = accountID .getAccountPropertyString(ProtocolProviderFactory.PREFERRED_TRANSPORT); if (proxyTransport != null && proxyTransport.length() > 0) { if (!proxyTransport.equals(ListeningPoint.UDP) && !proxyTransport.equals(ListeningPoint.TCP) && !proxyTransport.equals(ListeningPoint.TLS)) { throw new IllegalArgumentException(proxyTransport + " is not a valid transport protocol. Transport must be " + "left blanc or set to TCP, UDP or TLS."); } } else { proxyTransport = ListeningPoint.UDP; } StringBuffer proxyStringBuffer = new StringBuffer(proxyAddress.getHostAddress()); if(proxyAddress instanceof Inet6Address) { proxyStringBuffer.insert(0, '['); proxyStringBuffer.append(']'); } proxyStringBuffer.append(':'); proxyStringBuffer.append(Integer.toString(proxyPort)); proxyStringBuffer.append('/'); proxyStringBuffer.append(proxyTransport); //done parsing. init properties. this.outboundProxyString = proxyStringBuffer.toString(); //store a reference to our sip proxy so that we can use it when //constructing via and contact headers. this.outboundProxySocketAddress = new InetSocketAddress(proxyAddress, proxyPort); this.outboundProxyTransport = proxyTransport; } /** * Registers <tt>methodProcessor</tt> in the <tt>methorProcessors</tt> table * so that it would receives all messages in a transaction initiated by a * <tt>method</tt> request. If any previous processors exist for the same * method, they will be replaced by this one. * * @param method a String representing the SIP method that we're registering * the processor for (e.g. INVITE, REGISTER, or SUBSCRIBE). * @param methodProcessor a <tt>MethodProcessor</tt> implementation that * would handle all messages received within a <tt>method</tt> * transaction. */ public void registerMethodProcessor(String method, MethodProcessor methodProcessor) { List<MethodProcessor> processors = methodProcessors.get(method); if (processors == null) { processors = new LinkedList<MethodProcessor>(); methodProcessors.put(method, processors); } else { Class<? extends MethodProcessor> methodProcessorClass = methodProcessor.getClass(); for (Iterator<MethodProcessor> processorIter = processors.iterator(); processorIter.hasNext();) { if (processorIter.next().getClass() .equals(methodProcessorClass)) { processorIter.remove(); } } } processors.add(methodProcessor); } /** * Unregisters <tt>methodProcessor</tt> from the <tt>methorProcessors</tt> * table so that it won't receive further messages in a transaction * initiated by a <tt>method</tt> request. * * @param method the name of the method whose processor we'd like to * unregister. * @param methodProcessor */ public void unregisterMethodProcessor(String method, MethodProcessor methodProcessor) { List<MethodProcessor> processors = methodProcessors.get(method); if ((processors != null) && processors.remove(methodProcessor) && (processors.size() <= 0)) { methodProcessors.remove(method); } } /** * Returns the transport that we should use if we have no clear idea of our * destination's preferred transport. The method would first check if * we are running behind an outbound proxy and if so return its transport. * If no outbound proxy is set, the method would check the contents of the * DEFAULT_TRANSPORT property and return it if not null. Otherwise the * method would return UDP; * * @return The first non null password of the following: * a) the transport we use to communicate with our registrar * b) the transport of our outbound proxy, * c) the transport specified by the DEFAULT_TRANSPORT property, UDP. */ public String getDefaultTransport() { SipRegistrarConnection srConnection = getRegistrarConnection(); if( srConnection != null) { String registrarTransport = srConnection.getTransport(); if( registrarTransport != null && registrarTransport.length() > 0) { return registrarTransport; } } if(outboundProxySocketAddress != null && outboundProxyTransport != null) { return outboundProxyTransport; } else { String userSpecifiedDefaultTransport = SipActivator.getConfigurationService() .getString(DEFAULT_TRANSPORT); if(userSpecifiedDefaultTransport != null) { return userSpecifiedDefaultTransport; } else return ListeningPoint.UDP; } } /** * Returns the provider that corresponds to the transport returned by * getDefaultTransport(). Equivalent to calling * getJainSipProvider(getDefaultTransport()) * * @return the Jain SipProvider that corresponds to the transport returned * by getDefaultTransport(). */ public SipProvider getDefaultJainSipProvider() { return getJainSipProvider(getDefaultTransport()); } /** * Returns the listening point that corresponds to the transport returned by * getDefaultTransport(). Equivalent to calling * getListeningPoint(getDefaultTransport()) * * @return the Jain SipProvider that corresponds to the transport returned * by getDefaultTransport(). */ public ListeningPoint getDefaultListeningPoint() { return getListeningPoint(getDefaultTransport()); } /** * Returns the display name string that the user has set as a display name * for this account. * * @return the display name string that the user has set as a display name * for this account. */ public String getOurDisplayName() { return ourDisplayName; } /** * Returns a User Agent header that could be used for signing our requests. * * @return a <tt>UserAgentHeader</tt> that could be used for signing our * requests. */ public UserAgentHeader getSipCommUserAgentHeader() { if(userAgentHeader == null) { try { List<String> userAgentTokens = new LinkedList<String>(); userAgentTokens.add("Sip communicator"); userAgentTokens.add("1"); String osName = System.getProperty("os.name"); userAgentTokens.add(osName); userAgentHeader = this.headerFactory.createUserAgentHeader(userAgentTokens); } catch (ParseException ex) { //shouldn't happen return null; } } return userAgentHeader; } /** * Generates a ToTag and attaches it to the to header of <tt>response</tt>. * * @param response the response that is to get the ToTag. * @param containingDialog the Dialog instance that is to extract a unique * Tag value (containingDialog.hashCode()) */ public void attachToTag(Response response, Dialog containingDialog) { ToHeader to = (ToHeader) response.getHeader(ToHeader.NAME); if (to == null) { logger.debug("Strange ... no to tag in response:" + response); return; } if(containingDialog.getLocalTag() != null) { logger.debug("We seem to already have a tag in this dialog. " +"Returning"); return; } try { if (to.getTag() == null || to.getTag().trim().length() == 0) { String toTag = generateLocalTag(); logger.debug("generated to tag: " + toTag); to.setTag(toTag); } } catch (ParseException ex) { //a parse exception here mean an internal error so we can only log logger.error("Failed to attach a to tag to an outgoing response." , ex); } } /** * Returns a List of Strings corresponding to all methods that we have a * processor for. * @return a List of methods that we support. */ public List<String> getSupportedMethods() { return new ArrayList<String>(methodProcessors.keySet()); } private class ShutdownUnregistrationBlockListener implements RegistrationStateChangeListener { public List<RegistrationState> collectedNewStates = new LinkedList<RegistrationState>(); /** * The method would simply register all received events so that they * could be available for later inspection by the unit tests. In the * case where a registraiton event notifying us of a completed * registration is seen, the method would call notifyAll(). * * @param evt ProviderStatusChangeEvent the event describing the * status change. */ public void registrationStateChanged(RegistrationStateChangeEvent evt) { logger.debug("Received a RegistrationStateChangeEvent: " + evt); collectedNewStates.add(evt.getNewState()); if (evt.getNewState().equals(RegistrationState.UNREGISTERED)) { logger.debug( "We're unregistered and will notify those who wait"); synchronized (this) { notifyAll(); } } } /** * Blocks until an event notifying us of the awaited state change is * received or until waitFor miliseconds pass (whichever happens first). * * @param waitFor the number of miliseconds that we should be waiting * for an event before simply bailing out. */ public void waitForEvent(long waitFor) { logger.trace("Waiting for a " +"RegistrationStateChangeEvent.UNREGISTERED"); synchronized (this) { if (collectedNewStates.contains( RegistrationState.UNREGISTERED)) { logger.trace("Event already received. " + collectedNewStates); return; } try { wait(waitFor); if (collectedNewStates.size() > 0) logger.trace( "Received a RegistrationStateChangeEvent."); else logger.trace( "No RegistrationStateChangeEvent received for " + waitFor + "ms."); } catch (InterruptedException ex) { logger.debug( "Interrupted while waiting for a " +"RegistrationStateChangeEvent" , ex); } } } } /** * Returns the sip protocol icon. * @return the sip protocol icon */ public ProtocolIcon getProtocolIcon() { return protocolIcon; } /** * Returns the current instance of <tt>SipStatusEnum</tt>. * * @return the current instance of <tt>SipStatusEnum</tt>. */ SipStatusEnum getSipStatusEnum() { return sipStatusEnum; } /** * Returns the current instance of <tt>SipRegistrarConnection</tt>. * @return SipRegistrarConnection */ SipRegistrarConnection getRegistrarConnection() { return sipRegistrarConnection; } /** * Parses the the <tt>uriStr</tt> string and returns a JAIN SIP URI. * * @param uriStr a <tt>String</tt> containing the uri to parse. * * @return a URI object corresponding to the <tt>uriStr</tt> string. * @throws ParseException if uriStr is not properly formatted. */ public Address parseAddressString(String uriStr) throws ParseException { uriStr = uriStr.trim(); //we don't know how to handle the "tel:" scheme ... or rather we handle //it same as sip so replace: if(uriStr.toLowerCase().startsWith("tel:")) uriStr = "sip:" + uriStr.substring("tel:".length()); //Handle default domain name (i.e. transform 1234 -> 1234@sip.com) //assuming that if no domain name is specified then it should be the //same as ours. if (uriStr.indexOf('@') == -1) { //if we have a registrar, then we could append its domain name as //default SipRegistrarConnection src = getRegistrarConnection(); if(src != null && !src.isRegistrarless() ) { uriStr = uriStr + "@" + ((SipURI)src.getAddressOfRecord().getURI()).getHost(); } //else this could only be a host ... but this should work as is. } //Let's be uri fault tolerant and add the sip: scheme if there is none. if (!uriStr.toLowerCase().startsWith("sip:")) //no sip scheme { uriStr = "sip:" + uriStr; } Address toAddress = getAddressFactory().createAddress(uriStr); return toAddress; } /** * Tries to resolve <tt>address</tt> into a valid InetSocketAddress using * an <tt>SRV</tt> query where it exists and A/AAAA where it doesn't. * * @param address the address we'd like to resolve. * @param transport the protocol that we'd like to use when accessing * address. * * @return an <tt>InetSocketAddress</tt> instance containing the * <tt>SRV</tt> record for <tt>address</tt> if one has been defined and the * A/AAAA record where it hasn't. * * @throws UnknownHostException if <tt>address</tt> is not a valid host * address. */ public InetSocketAddress resolveSipAddress(String address, String transport) throws UnknownHostException { InetSocketAddress sockAddr = null; //we need to resolve the address only if its a hostname. if(NetworkUtils.isValidIPAddress(address)) { InetAddress addressObj = NetworkUtils.getInetAddress(address); //this is an ip address so we need to return default ports since //we can't get them from a DNS. int port = ListeningPoint.PORT_5060; if(transport.equalsIgnoreCase(ListeningPoint.TLS)) port = ListeningPoint.PORT_5061; return new InetSocketAddress(addressObj, port); } //try to obtain SRV mappings from the DNS try { if(transport.equalsIgnoreCase(ListeningPoint.TLS)) { sockAddr = NetworkUtils.getSRVRecord( "sips", ListeningPoint.TCP, address); } else { sockAddr = NetworkUtils.getSRVRecord("sip", transport, address); } logger.trace("Returned SRV " + sockAddr); } catch (ParseException e) { throw new UnknownHostException(address); } if(sockAddr != null) return sockAddr; //there were no SRV mappings so we only need to A/AAAA resolve the //address. Do this before we instantiate the resulting InetSocketAddress //because its constructor suprresses UnknownHostException-s and we want //to know if something goes wrong. InetAddress addressObj = InetAddress.getByName(address); //no SRV means default ports int defaultPort = ListeningPoint.PORT_5060; if(transport.equalsIgnoreCase(ListeningPoint.TLS)) defaultPort = ListeningPoint.PORT_5061; return new InetSocketAddress(addressObj, defaultPort); } /** * Tries to resolve <tt>address</tt> into a valid InetSocketAddress using * an <tt>SRV</tt> query where it exists and A/AAAA where it doesn't. The * method assumes that the transport that we'll be using when connecting to * address is the one that has been defined as default for this provider. * * @param address the address we'd like to resolve. * * @return an <tt>InetSocketAddress</tt> instance containing the * <tt>SRV</tt> record for <tt>address</tt> if one has been defined and the * A/AAAA record where it hasn't. * * @throws UnknownHostException if <tt>address</tt> is not a valid host * address. */ public InetSocketAddress resolveSipAddress(String address) throws UnknownHostException { return resolveSipAddress(address, getDefaultTransport()); } /** * Returns the <tt>InetAddress</tt> that is most likely to be to be used * as a next hop when contacting the specified <tt>destination</tt>. This is * an utility method that is used whenever we have to choose one of our * local addresses to put in the Via, Contact or (in the case of no * registrar accounts) From headers. * * @param destination the destination that we would contact. * * @return the <tt>InetAddress</tt> that is most likely to be to be used * as a next hop when contacting the specified <tt>destination</tt>. * * @throws IllegalArgumentException if <tt>destination</tt> is not a valid * host/ip/fqdn */ private InetAddress getIntendedDestination(SipURI destination) throws IllegalArgumentException { return getIntendedDestination(destination.getHost()); } /** * Returns the <tt>InetAddress</tt> that is most likely to be to be used * as a next hop when contacting the specified <tt>destination</tt>. This is * an utility method that is used whenever we have to choose one of our * local addresses to put in the Via, Contact or (in the case of no * registrar accounts) From headers. * * @param host the destination that we would contact. * * @return the <tt>InetAddress</tt> that is most likely to be to be used * as a next hop when contacting the specified <tt>destination</tt>. * * @throws IllegalArgumentException if <tt>destination</tt> is not a valid * host/ip/fqdn. */ private InetAddress getIntendedDestination(String host) throws IllegalArgumentException { // Address InetAddress destinationInetAddress = null; //resolveSipAddress() verifies whether our destination is valid //but the destination could only be known to our outbound proxy //if we have one. If this is the case replace the destination //address with that of the proxy.(report by Dan Bogos) if(getOutboundProxy() != null) { logger.trace("Will use proxy address"); destinationInetAddress = getOutboundProxy().getAddress(); } else { try { destinationInetAddress = resolveSipAddress(host).getAddress(); } catch (UnknownHostException ex) { throw new IllegalArgumentException( host + " is not a valid internet address.", ex); } } if(logger.isDebugEnabled()) logger.debug("Returning address " + destinationInetAddress + " for destination " + host); return destinationInetAddress; } /** * Stops dispatching SIP messages to a SIP protocol provider service * once it's been unregistered. * * @param event the change event in the registration state of a provider. */ public void registrationStateChanged(RegistrationStateChangeEvent event) { if(event.getNewState() == RegistrationState.UNREGISTERED) { ProtocolProviderServiceSipImpl listener = (ProtocolProviderServiceSipImpl) event.getProvider(); sipStackSharing.removeSipListener(listener); listener.removeRegistrationStateChangeListener(this); } } }