/* * 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 javax.sip.*; import javax.sip.message.*; import javax.sip.address.*; import javax.sip.header.*; import java.text.*; import java.util.*; import java.io.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; /** * Handles OPTIONS requests by replying with an OK response containing * methods that we support. * * @author Emil Ivov */ public class ClientCapabilities implements MethodProcessor { private static Logger logger = Logger.getLogger(ClientCapabilities.class); /** * The protocol provider that created us. */ private ProtocolProviderServiceSipImpl provider = null; /** * The timer that runs the keep-alive task */ private Timer keepAliveTimer = null; /** * The next long to use as a cseq header value. */ private long nextCSeqValue = 1; public ClientCapabilities(ProtocolProviderServiceSipImpl protocolProvider) { this.provider = protocolProvider; provider.registerMethodProcessor(Request.OPTIONS, this); provider.addRegistrationStateChangeListener(new RegistrationListener()); } /** * Receives options requests and replies with an OK response containing * methods that we support. * * @param requestEvent the incoming options request. */ public boolean processRequest(RequestEvent requestEvent) { Response optionsOK = null; try { optionsOK = provider.getMessageFactory().createResponse( Response.OK, requestEvent.getRequest()); Iterator<String> supportedMethods = provider.getSupportedMethods().iterator(); //add to the allows header all methods that we support while(supportedMethods.hasNext()) { String method = (String)supportedMethods.next(); //don't support REGISTERs if(method.equals(Request.REGISTER)) continue; optionsOK.addHeader( provider.getHeaderFactory().createAllowHeader(method)); } Iterator<String> events = provider.getKnownEventsList().iterator(); synchronized (provider.getKnownEventsList()) { while (events.hasNext()) { String event = events.next(); optionsOK.addHeader(provider.getHeaderFactory() .createAllowEventsHeader(event)); } } //add a user agent header. optionsOK.setHeader(provider.getSipCommUserAgentHeader()); } catch (ParseException ex) { //What else could we do apart from logging? logger.warn("Failed to create an incoming OPTIONS request", ex); return false; } try { ServerTransaction sTran = requestEvent.getServerTransaction(); if (sTran == null) { SipProvider sipProvider = (SipProvider)requestEvent.getSource(); sTran = sipProvider .getNewServerTransaction(requestEvent.getRequest()); } sTran.sendResponse(optionsOK); } catch(TransactionUnavailableException ex) { //this means that we received an OPTIONS request outside the scope //of a transaction which could mean that someone is simply sending //us b***shit to keep a NAT connection alive, so let's not get too //excited. logger.info("Failed to respond to an incoming " +"transactionless OPTIONS request"); logger.trace("Exception was:", ex); return false; } catch (InvalidArgumentException ex) { //What else could we do apart from logging? logger.warn("Failed to send an incoming OPTIONS request", ex); return false; } catch (SipException ex) { //What else could we do apart from logging? logger.warn("Failed to send an incoming OPTIONS request", ex); return false; } return true; } /** * ignore. don't needed. * @param dialogTerminatedEvent unused */ public boolean processDialogTerminated( DialogTerminatedEvent dialogTerminatedEvent) { return false; } /** * ignore. don't needed. * @param exceptionEvent unused */ public boolean processIOException(IOExceptionEvent exceptionEvent) { return false; } /** * ignore for the time being * @param responseEvent unused */ public boolean processResponse(ResponseEvent responseEvent) { return false; } /** * ignore for the time being. * @param timeoutEvent unused */ public boolean processTimeout(TimeoutEvent timeoutEvent) { disconnect(); return true; } /** * ignore for the time being. * @param transactionTerminatedEvent unused */ public boolean processTransactionTerminated( TransactionTerminatedEvent transactionTerminatedEvent) { return false; } /** * Returns the next long to use as a cseq header value. * @return the next long to use as a cseq header value. */ private long getNextCSeqValue() { return nextCSeqValue++; } /** * Fire event that connection has failed and we had to unregister * the protocol provider. */ private void disconnect() { //don't alert the user if we're already off if(provider.getRegistrationState() .equals(RegistrationState.UNREGISTERED)) { return; } provider.getRegistrarConnection().setRegistrationState( RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED , "A timeout occurred while trying to connect to the server."); } /** * The task would continuously send OPTIONs request that we use as a keep * alive method. */ private class KeepAliveTask extends TimerTask { public void run() { try { logger.logEntry(); //From FromHeader fromHeader = null; try { //this keep alive task only makes sense in case we have //a registrar so we deliberately use our AOR and do not //use the getOurSipAddress() method. fromHeader = provider.getHeaderFactory().createFromHeader( provider.getRegistrarConnection().getAddressOfRecord(), ProtocolProviderServiceSipImpl.generateLocalTag()); } catch (ParseException ex) { //this should never happen so let's just log and bail. logger.error("Failed to generate a from header for " + "our register request." , ex); return; } //Call ID Header CallIdHeader callIdHeader = provider.getDefaultJainSipProvider().getNewCallId(); //CSeq Header CSeqHeader cSeqHeader = null; try { cSeqHeader = provider.getHeaderFactory().createCSeqHeader( getNextCSeqValue(), Request.OPTIONS); } catch (ParseException ex) { //Should never happen logger.error("Corrupt Sip Stack", ex); return; } catch (InvalidArgumentException ex) { //Should never happen logger.error("The application is corrupt", ex); return; } //To Header ToHeader toHeader = null; try { //this request isn't really going anywhere so we put our //own address in the To Header. toHeader = provider.getHeaderFactory().createToHeader( fromHeader.getAddress(), null); } catch (ParseException ex) { logger.error("Could not create a To header for address:" + fromHeader.getAddress(), ex); return; } //MaxForwardsHeader MaxForwardsHeader maxForwardsHeader = provider. getMaxForwardsHeader(); //Request Request request = null; try { //create a host-only uri for the request uri header. String domain = ((SipURI) toHeader.getAddress().getURI()).getHost(); //request URI SipURI requestURI = provider.getAddressFactory() .createSipURI(null, domain); //Via Headers ArrayList<ViaHeader> viaHeaders = provider .getLocalViaHeaders(requestURI); request = provider.getMessageFactory().createRequest( requestURI , Request.OPTIONS , callIdHeader , cSeqHeader , fromHeader , toHeader , viaHeaders , maxForwardsHeader); if (logger.isDebugEnabled()) logger.debug("Created OPTIONS request " + request); } catch (ParseException ex) { logger.error("Could not create an OPTIONS request!", ex); return; } Iterator<String> supportedMethods = provider.getSupportedMethods().iterator(); //add to the allows header all methods that we support while(supportedMethods.hasNext()) { String method = (String)supportedMethods.next(); //don't support REGISTERs if(method.equals(Request.REGISTER)) continue; request.addHeader( provider.getHeaderFactory().createAllowHeader(method)); } Iterator<String> events = provider.getKnownEventsList().iterator(); synchronized (provider.getKnownEventsList()) { while (events.hasNext()) { String event = (String) events.next(); request.addHeader(provider.getHeaderFactory() .createAllowEventsHeader(event)); } } //User Agent UserAgentHeader userAgentHeader = provider.getSipCommUserAgentHeader(); if(userAgentHeader != null) request.addHeader(userAgentHeader); //Contact Header (should contain IP) ContactHeader contactHeader = provider .getContactHeader((SipURI)request.getRequestURI()); request.addHeader(contactHeader); //Transaction ClientTransaction optionsTrans = null; try { optionsTrans = provider.getDefaultJainSipProvider() .getNewClientTransaction(request); } catch (TransactionUnavailableException ex) { logger.error("Could not create a register transaction!\n" + "Check that the Registrar address is correct!", ex); return; } try { optionsTrans.sendRequest(); logger.debug("sent request= " + request); } catch (SipException ex) { logger.error("Could not send out the options request!", ex); if(ex.getCause() instanceof IOException) { // IOException problem with network disconnect(); } return; } }catch(Exception ex) { logger.error("Cannot send OPTIONS keep alive", ex); } } } private class RegistrationListener implements RegistrationStateChangeListener { /** * The method is called by a ProtocolProvider implementation whenever * a change in the registration state of the corresponding provider had * occurred. The method is particularly interested in events stating * that the SIP provider has unregistered so that it would fire * status change events for all contacts in our buddy list. * * @param evt ProviderStatusChangeEvent the event describing the status * change. */ public void registrationStateChanged(RegistrationStateChangeEvent evt) { if(evt.getNewState() == RegistrationState.UNREGISTERING || evt.getNewState() == RegistrationState.CONNECTION_FAILED) { // stop any task associated with the timer if (keepAliveTimer != null) { keepAliveTimer.cancel(); keepAliveTimer = null; } } else if (evt.getNewState().equals(RegistrationState.REGISTERED)) { String keepAliveMethod = provider.getAccountID().getAccountPropertyString( ProtocolProviderServiceSipImpl.KEEP_ALIVE_METHOD); logger.trace("Keep alive method " + keepAliveMethod); if(keepAliveMethod == null || !keepAliveMethod.equalsIgnoreCase("options")) return; int keepAliveInterval = provider.getAccountID().getAccountPropertyInt( ProtocolProviderServiceSipImpl.KEEP_ALIVE_INTERVAL, -1); logger.trace("Keep alive inerval is " + keepAliveInterval); if (keepAliveInterval > 0 && !provider.getRegistrarConnection().isRegistrarless()) { if (keepAliveTimer == null) keepAliveTimer = new Timer(); logger.debug("Scheduling OPTIONS keep alives"); keepAliveTimer.schedule(new KeepAliveTask(), 0, keepAliveInterval * 1000); } } } } }