Package gov.nist.javax.sip.stack

Source Code of gov.nist.javax.sip.stack.SIPTransaction$LingerTimer

/*
* Conditions Of Use
*
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), an agency of the Federal Government.
* Pursuant to title 15 Untied States Code Section 105, works of NIST
* employees are not subject to copyright protection in the United States
* and are considered to be in the public domain.  As a result, a formal
* license is not needed to use the software.
*
* This software is provided by NIST as a service and is expressly
* provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY.  NIST does not warrant or make any representations
* regarding the use of the software or the results thereof, including but
* not limited to the correctness, accuracy, reliability or usefulness of
* the software.
*
* Permission to use this software is contingent upon your acceptance
* of the terms of this agreement
*
* .
*
*/
package gov.nist.javax.sip.stack;

import gov.nist.core.InternalErrorHandler;
import gov.nist.core.LogWriter;
import gov.nist.javax.sip.address.AddressFactoryImpl;
import gov.nist.core.ServerLogger;
import gov.nist.javax.sip.SIPConstants;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.header.CallID;
import gov.nist.javax.sip.header.Event;
import gov.nist.javax.sip.header.From;
import gov.nist.javax.sip.header.To;
import gov.nist.javax.sip.header.Via;
import gov.nist.javax.sip.header.ViaList;
import gov.nist.javax.sip.message.SIPMessage;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;

import java.io.IOException;
import java.net.InetAddress;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.sip.Dialog;
import javax.sip.IOExceptionEvent;
import javax.sip.ServerTransaction;
import javax.sip.TransactionState;
import javax.sip.address.SipURI;
import javax.sip.message.Request;
import javax.sip.message.Response;

/*
* Modifications for TLS Support added by Daniel J. Martinez Manzano
* <dani@dif.um.es> Bug fixes by Jeroen van Bemmel (JvB) and others.
*/

/**
* Abstract class to support both client and server transactions. Provides an
* encapsulation of a message channel, handles timer events, and creation of the
* Via header for a message.
*
* @author Jeff Keyser
* @author M. Ranganathan
*
*
* @version 1.2 $Revision: 1.75.2.7 $ $Date: 2010/11/23 19:23:14 $
*/
public abstract class SIPTransaction extends MessageChannel implements
        javax.sip.Transaction, gov.nist.javax.sip.TransactionExt {

    protected boolean toListener; // Flag to indicate that the listener gets

    // to see the event.

    protected int BASE_TIMER_INTERVAL = SIPTransactionStack.BASE_TIMER_INTERVAL;
    /**
     * 5 sec Maximum duration a message will remain in the network
     */
    protected int T4 = 5000 / BASE_TIMER_INTERVAL;

    /**
     * The maximum retransmit interval for non-INVITE requests and INVITE
     * responses
     */
    protected int T2 = 4000 / BASE_TIMER_INTERVAL;
    protected int TIMER_I = T4;

    protected int TIMER_K = T4;

    protected int TIMER_D = 32000 / BASE_TIMER_INTERVAL;

    // protected static final int TIMER_C = 3 * 60 * 1000 / BASE_TIMER_INTERVAL;

    /**
     * One timer tick.
     */
    protected static final int T1 = 1;

    /**
     * INVITE request retransmit interval, for UDP only
     */
    protected static final int TIMER_A = 1;

    /**
     * INVITE transaction timeout timer
     */
    protected static final int TIMER_B = 64;

    protected static final int TIMER_J = 64;

    protected static final int TIMER_F = 64;

    protected static final int TIMER_H = 64;

    // Proposed feature for next release.
    protected transient Object applicationData;

    protected SIPResponse lastResponse;

    // private SIPDialog dialog;

    protected boolean isMapped;

    private Semaphore semaphore;

    protected boolean isSemaphoreAquired;

    // protected boolean eventPending; // indicate that an event is pending
    // here.

    protected String transactionId; // Transaction Id.

    // Audit tag used by the SIP Stack audit
    public long auditTag = 0;

    /**
     * Initialized but no state assigned.
     */
    public static final TransactionState INITIAL_STATE = null;

    /**
     * Trying state.
     */
    public static final TransactionState TRYING_STATE = TransactionState.TRYING;

    /**
     * CALLING State.
     */
    public static final TransactionState CALLING_STATE = TransactionState.CALLING;

    /**
     * Proceeding state.
     */
    public static final TransactionState PROCEEDING_STATE = TransactionState.PROCEEDING;

    /**
     * Completed state.
     */
    public static final TransactionState COMPLETED_STATE = TransactionState.COMPLETED;

    /**
     * Confirmed state.
     */
    public static final TransactionState CONFIRMED_STATE = TransactionState.CONFIRMED;

    /**
     * Terminated state.
     */
    public static final TransactionState TERMINATED_STATE = TransactionState.TERMINATED;

    /**
     * Maximum number of ticks between retransmissions.
     */
    protected static final int MAXIMUM_RETRANSMISSION_TICK_COUNT = 8;

    // Parent stack for this transaction
    protected transient SIPTransactionStack sipStack;

    // Original request that is being handled by this transaction
    protected SIPRequest originalRequest;

    // Underlying channel being used to send messages for this transaction
    private transient MessageChannel encapsulatedChannel;

    // Port of peer
    protected int peerPort;

    // Address of peer
    protected InetAddress peerInetAddress;

    // Address of peer as a string
    protected String peerAddress;

    // Protocol of peer
    protected String peerProtocol;

    // @@@ hagai - NAT changes
    // Source port extracted from peer packet
    protected int peerPacketSourcePort;

    protected InetAddress peerPacketSourceAddress;

    protected AtomicBoolean transactionTimerStarted = new AtomicBoolean(false);

    // Transaction branch ID
    private String branch;

    // Method of the Request used to create the transaction.
    private String method;

    // Sequence number of request used to create the transaction
    private long cSeq;

    // Current transaction state
    private TransactionState currentState;

    // Number of ticks the retransmission timer was set to last
    private transient int retransmissionTimerLastTickCount;

    // Number of ticks before the message is retransmitted
    private transient int retransmissionTimerTicksLeft;

    // Number of ticks before the transaction times out
    protected int timeoutTimerTicksLeft;

    // List of event listeners for this transaction
    private transient Set<SIPTransactionEventListener> eventListeners;

    // Hang on to these - we clear out the request URI after
    // transaction goes to final state. Pointers to these are kept around
    // for transaction matching as long as the transaction is in
    // the transaction table.
    protected From from;

    protected To to;

    protected Event event;

    protected CallID callId;

    // Back ptr to the JAIN layer.
    // private Object wrapper;

    // Counter for caching of connections.
    // Connection lingers for collectionTime
    // after the Transaction goes to terminated state.
    protected int collectionTime;

    protected String toTag;

    protected String fromTag;

    private boolean terminatedEventDelivered;

    public String getBranchId() {
        return this.branch;
    }

    /**
     * The linger timer is used to remove the transaction from the transaction
     * table after it goes into terminated state. This allows connection caching
     * and also takes care of race conditins.
     *
     *
     */
    class LingerTimer extends SIPStackTimerTask {

        public LingerTimer() {
            SIPTransaction sipTransaction = SIPTransaction.this;
            if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                sipStack.getStackLogger().logDebug("LingerTimer : "
                        + sipTransaction.getTransactionId());
            }

        }

        protected void runTask() {
            SIPTransaction transaction = SIPTransaction.this;
            // release the connection associated with this transaction.
            SIPTransactionStack sipStack = transaction.getSIPStack();

            if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                sipStack.getStackLogger().logDebug("LingerTimer: run() : "
                        + getTransactionId());
            }

            if (transaction instanceof SIPClientTransaction) {
                sipStack.removeTransaction(transaction);
                transaction.close();

            } else if (transaction instanceof ServerTransaction) {
                // Remove it from the set
                if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                    sipStack.getStackLogger().logDebug("removing" + transaction);
                sipStack.removeTransaction(transaction);
                if ((!sipStack.cacheServerConnections)
                        && --transaction.encapsulatedChannel.useCount <= 0) {
                    // Close the encapsulated socket if stack is configured
                    transaction.close();
                } else {
                    if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)
                            && (!sipStack.cacheServerConnections)
                            && transaction.isReliable()) {
                        int useCount = transaction.encapsulatedChannel.useCount;
                        sipStack.getStackLogger().logDebug("Use Count = " + useCount);
                    }
                }
            }

        }
    }

    /**
     * Transaction constructor.
     *
     * @param newParentStack
     *            Parent stack for this transaction.
     * @param newEncapsulatedChannel
     *            Underlying channel for this transaction.
     */
    protected SIPTransaction(SIPTransactionStack newParentStack,
            MessageChannel newEncapsulatedChannel) {

        sipStack = newParentStack;
        this.semaphore = new Semaphore(1,true);
       
        encapsulatedChannel = newEncapsulatedChannel;
        // Record this to check if the address has changed before sending
        // message to avoid possible race condition.
        this.peerPort = newEncapsulatedChannel.getPeerPort();
        this.peerAddress = newEncapsulatedChannel.getPeerAddress();
        this.peerInetAddress = newEncapsulatedChannel.getPeerInetAddress();
        // @@@ hagai
        this.peerPacketSourcePort = newEncapsulatedChannel
                .getPeerPacketSourcePort();
        this.peerPacketSourceAddress = newEncapsulatedChannel
                .getPeerPacketSourceAddress();
        this.peerProtocol = newEncapsulatedChannel.getPeerProtocol();
        if (this.isReliable()) {           
                encapsulatedChannel.useCount++;
                if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                    sipStack.getStackLogger()
                            .logDebug("use count for encapsulated channel"
                                    + this
                                    + " "
                                    + encapsulatedChannel.useCount );
        }

        this.currentState = null;

        disableRetransmissionTimer();
        disableTimeoutTimer();
        eventListeners = Collections.synchronizedSet(new HashSet<SIPTransactionEventListener>());

        // Always add the parent stack as a listener
        // of this transaction
        addEventListener(newParentStack);

    }

    /**
     * Sets the request message that this transaction handles.
     *
     * @param newOriginalRequest
     *            Request being handled.
     */
    public void setOriginalRequest(SIPRequest newOriginalRequest) {

        // Branch value of topmost Via header
        String newBranch;

        if (this.originalRequest != null
                && (!this.originalRequest.getTransactionId().equals(
                        newOriginalRequest.getTransactionId()))) {
            sipStack.removeTransactionHash(this);
        }
        // This will be cleared later.

        this.originalRequest = newOriginalRequest;

        // just cache the control information so the
        // original request can be released later.
        this.method = newOriginalRequest.getMethod();
        this.from = (From) newOriginalRequest.getFrom();
        this.to = (To) newOriginalRequest.getTo();
        // Save these to avoid concurrent modification exceptions!
        this.toTag = this.to.getTag();
        this.fromTag = this.from.getTag();
        this.callId = (CallID) newOriginalRequest.getCallId();
        this.cSeq = newOriginalRequest.getCSeq().getSeqNumber();
        this.event = (Event) newOriginalRequest.getHeader("Event");
        this.transactionId = newOriginalRequest.getTransactionId();

        originalRequest.setTransaction(this);

        // If the message has an explicit branch value set,
        newBranch = ((Via) newOriginalRequest.getViaHeaders().getFirst())
                .getBranch();
        if (newBranch != null) {
            if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                sipStack.getStackLogger().logDebug("Setting Branch id : " + newBranch);

            // Override the default branch with the one
            // set by the message
            setBranch(newBranch);

        } else {
            if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                sipStack.getStackLogger().logDebug("Branch id is null - compute TID!"
                        + newOriginalRequest.encode());
            setBranch(newOriginalRequest.getTransactionId());
        }
    }

    /**
     * Gets the request being handled by this transaction.
     *
     * @return -- the original Request associated with this transaction.
     */
    public SIPRequest getOriginalRequest() {
        return originalRequest;
    }

    /**
     * Get the original request but cast to a Request structure.
     *
     * @return the request that generated this transaction.
     */
    public Request getRequest() {
        return (Request) originalRequest;
    }

    /**
     * Returns a flag stating whether this transaction is for an INVITE request
     * or not.
     *
     * @return -- true if this is an INVITE request, false if not.
     */
    public final boolean isInviteTransaction() {
        return getMethod().equals(Request.INVITE);
    }

    /**
     * Return true if the transaction corresponds to a CANCEL message.
     *
     * @return -- true if the transaciton is a CANCEL transaction.
     */
    public final boolean isCancelTransaction() {
        return getMethod().equals(Request.CANCEL);
    }

    /**
     * Return a flag that states if this is a BYE transaction.
     *
     * @return true if the transaciton is a BYE transaction.
     */
    public final boolean isByeTransaction() {
        return getMethod().equals(Request.BYE);
    }

    /**
     * Returns the message channel used for transmitting/receiving messages for
     * this transaction. Made public in support of JAIN dual transaction model.
     *
     * @return Encapsulated MessageChannel.
     *
     */
    public MessageChannel getMessageChannel() {
        return encapsulatedChannel;
    }

    /**
     * Sets the Via header branch parameter used to identify this transaction.
     *
     * @param newBranch
     *            New string used as the branch for this transaction.
     */
    public final void setBranch(String newBranch) {
        branch = newBranch;
    }

    /**
     * Gets the current setting for the branch parameter of this transaction.
     *
     * @return Branch parameter for this transaction.
     */
    public final String getBranch() {
        if (this.branch == null) {
            this.branch = getOriginalRequest().getTopmostVia().getBranch();
        }
        return branch;
    }

    /**
     * Get the method of the request used to create this transaction.
     *
     * @return the method of the request for the transaction.
     */
    public final String getMethod() {
        return this.method;
    }

    /**
     * Get the Sequence number of the request used to create the transaction.
     *
     * @return the cseq of the request used to create the transaction.
     */
    public final long getCSeq() {
        return this.cSeq;
    }

    /**
     * Changes the state of this transaction.
     *
     * @param newState
     *            New state of this transaction.
     */
    public void setState(TransactionState newState) {
        // PATCH submitted by sribeyron
        if (currentState == TransactionState.COMPLETED) {
            if (newState != TransactionState.TERMINATED
                    && newState != TransactionState.CONFIRMED)
                newState = TransactionState.COMPLETED;
        }
        if (currentState == TransactionState.CONFIRMED) {
            if (newState != TransactionState.TERMINATED)
                newState = TransactionState.CONFIRMED;
        }
        if (currentState != TransactionState.TERMINATED)
            currentState = newState;
        else
            newState = currentState;
        // END OF PATCH
        if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            sipStack.getStackLogger().logDebug("Transaction:setState " + newState
                    + " " + this + " branchID = " + this.getBranch()
                    + " isClient = " + (this instanceof SIPClientTransaction));
            sipStack.getStackLogger().logStackTrace();
        }
    }

    /**
     * Gets the current state of this transaction.
     *
     * @return Current state of this transaction.
     */
    public TransactionState getState() {
        return this.currentState;
    }

    /**
     * Enables retransmission timer events for this transaction to begin in one
     * tick.
     */
    protected final void enableRetransmissionTimer() {
        enableRetransmissionTimer(1);
    }

    /**
     * Enables retransmission timer events for this transaction to begin after
     * the number of ticks passed to this routine.
     *
     * @param tickCount
     *            Number of ticks before the next retransmission timer event
     *            occurs.
     */
    protected final void enableRetransmissionTimer(int tickCount) {
        // For INVITE Client transactions, double interval each time
        if (isInviteTransaction() && (this instanceof SIPClientTransaction)) {
            retransmissionTimerTicksLeft = tickCount;
        } else {
            // non-INVITE transactions and 3xx-6xx responses are capped at T2
            retransmissionTimerTicksLeft = Math.min(tickCount,
                    MAXIMUM_RETRANSMISSION_TICK_COUNT);
        }
        retransmissionTimerLastTickCount = retransmissionTimerTicksLeft;
    }

    /**
     * Turns off retransmission events for this transaction.
     */
    protected final void disableRetransmissionTimer() {
        retransmissionTimerTicksLeft = -1;
    }

    /**
     * Enables a timeout event to occur for this transaction after the number of
     * ticks passed to this method.
     *
     * @param tickCount
     *            Number of ticks before this transaction times out.
     */
    protected final void enableTimeoutTimer(int tickCount) {
        if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            sipStack.getStackLogger().logDebug("enableTimeoutTimer " + this
                    + " tickCount " + tickCount + " currentTickCount = "
                    + timeoutTimerTicksLeft);

        timeoutTimerTicksLeft = tickCount;
    }

    /**
     * Disabled the timeout timer.
     */
    protected final void disableTimeoutTimer() {
        timeoutTimerTicksLeft = -1;
    }

    /**
     * Fired after each timer tick. Checks the retransmission and timeout timers
     * of this transaction, and fired these events if necessary.
     */
    final void fireTimer() {
        // If the timeout timer is enabled,

        if (timeoutTimerTicksLeft != -1) {
            // Count down the timer, and if it has run out,
            if (--timeoutTimerTicksLeft == 0) {
                // Fire the timeout timer
                fireTimeoutTimer();
            }
        }

        // If the retransmission timer is enabled,
        if (retransmissionTimerTicksLeft != -1) {
            // Count down the timer, and if it has run out,
            if (--retransmissionTimerTicksLeft == 0) {
                // Enable this timer to fire again after
                // twice the original time
                enableRetransmissionTimer(retransmissionTimerLastTickCount * 2);
                // Fire the timeout timer
                fireRetransmissionTimer();
            }
        }
    }

    /**
     * Tests if this transaction has terminated.
     *
     * @return Trus if this transaction is terminated, false if not.
     */
    public final boolean isTerminated() {
        return getState() == TERMINATED_STATE;
    }

    public String getHost() {
        return encapsulatedChannel.getHost();
    }

    public String getKey() {
        return encapsulatedChannel.getKey();
    }

    public int getPort() {
        return encapsulatedChannel.getPort();
    }

    public SIPTransactionStack getSIPStack() {
        return (SIPTransactionStack) sipStack;
    }

    public String getPeerAddress() {
        return this.peerAddress;
    }

    public int getPeerPort() {
        return this.peerPort;
    }

    // @@@ hagai
    public int getPeerPacketSourcePort() {
        return this.peerPacketSourcePort;
    }

    public InetAddress getPeerPacketSourceAddress() {
        return this.peerPacketSourceAddress;
    }

    protected InetAddress getPeerInetAddress() {
        return this.peerInetAddress;
    }

    protected String getPeerProtocol() {
        return this.peerProtocol;
    }

    public String getTransport() {
        return encapsulatedChannel.getTransport();
    }

    public boolean isReliable() {
        return encapsulatedChannel.isReliable();
    }

    /**
     * Returns the Via header for this channel. Gets the Via header of the
     * underlying message channel, and adds a branch parameter to it for this
     * transaction.
     */
    public Via getViaHeader() {
        // Via header of the encapulated channel
        Via channelViaHeader;

        // Add the branch parameter to the underlying
        // channel's Via header
        channelViaHeader = super.getViaHeader();
        try {
            channelViaHeader.setBranch(branch);
        } catch (java.text.ParseException ex) {
        }
        return channelViaHeader;

    }

    /**
     * Process the message through the transaction and sends it to the SIP peer.
     *
     * @param messageToSend
     *            Message to send to the SIP peer.
     */
    public void sendMessage(final SIPMessage messageToSend) throws IOException {
        // Use the peer address, port and transport
        // that was specified when the transaction was
        // created. Bug was noted by Bruce Evangelder
        // soleo communications.
        try {
          final RawMessageChannel channel = (RawMessageChannel) encapsulatedChannel;
            for (MessageProcessor messageProcessor : sipStack
                    .getMessageProcessors()) {
              boolean addrmatch = messageProcessor.getIpAddress().getHostAddress().toString().equals(this.getPeerAddress());
              if (addrmatch
                  && messageProcessor.getPort() == this.getPeerPort()
                  && messageProcessor.getTransport().equalsIgnoreCase(
                      this.getPeerProtocol())) {
                if (channel instanceof TCPMessageChannel) {
                  try {

                    Runnable processMessageTask = new Runnable() {

                      public void run() {
                        try {
                          ((TCPMessageChannel) channel)
                          .processMessage((SIPMessage) messageToSend.clone(), getPeerInetAddress());
                        } catch (Exception ex) {
                          if (getSIPStack().getStackLogger().isLoggingEnabled(ServerLogger.TRACE_ERROR)) {
                            getSIPStack().getStackLogger().logError("Error self routing message cause by: ", ex);
                          }
                        }
                      }
                    };
                    getSIPStack().getSelfRoutingThreadpoolExecutor().execute(processMessageTask);

                  } catch (Exception e) {
                    sipStack.getStackLogger().logError("Error passing message in self routing", e);
                  }
                  if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                          sipStack.getStackLogger().logDebug("Self routing message");
                        return;
                    }
                    if (channel instanceof RawMessageChannel) {
                        try {
                         
                          Runnable processMessageTask = new Runnable() {
                 
                  public void run() {
                    try {
                      ((RawMessageChannel) channel).processMessage((SIPMessage) messageToSend.clone());
                    } catch (Exception ex) {
                      if (getSIPStack().getStackLogger().isLoggingEnabled(ServerLogger.TRACE_ERROR)) {
                            getSIPStack().getStackLogger().logError("Error self routing message cause by: ", ex);
                          }
                    }
                  }
                };
                getSIPStack().getSelfRoutingThreadpoolExecutor().execute(processMessageTask);
            } catch (Exception e) {
              sipStack.getStackLogger().logError("Error passing message in self routing", e);
            }
                        if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                          sipStack.getStackLogger().logDebug("Self routing message");
                        return;
                    }

                }
            }
            encapsulatedChannel.sendMessage(messageToSend,
                    this.peerInetAddress, this.peerPort);
        } finally {
            this.startTransactionTimer();
        }
    }

    /**
     * Parse the byte array as a message, process it through the transaction,
     * and send it to the SIP peer. This is just a placeholder method -- calling
     * it will result in an IO exception.
     *
     * @param messageBytes
     *            Bytes of the message to send.
     * @param receiverAddress
     *            Address of the target peer.
     * @param receiverPort
     *            Network port of the target peer.
     *
     * @throws IOException
     *             If called.
     */
    protected void sendMessage(byte[] messageBytes,
            InetAddress receiverAddress, int receiverPort, boolean retry)
            throws IOException {
        throw new IOException(
                "Cannot send unparsed message through Transaction Channel!");
    }

    /**
     * Adds a new event listener to this transaction.
     *
     * @param newListener
     *            Listener to add.
     */
    public void addEventListener(SIPTransactionEventListener newListener) {
        eventListeners.add(newListener);
    }

    /**
     * Removed an event listener from this transaction.
     *
     * @param oldListener
     *            Listener to remove.
     */
    public void removeEventListener(SIPTransactionEventListener oldListener) {
        eventListeners.remove(oldListener);
    }

    /**
     * Creates a SIPTransactionErrorEvent and sends it to all of the listeners
     * of this transaction. This method also flags the transaction as
     * terminated.
     *
     * @param errorEventID
     *            ID of the error to raise.
     */
    protected void raiseErrorEvent(int errorEventID) {

        // Error event to send to all listeners
        SIPTransactionErrorEvent newErrorEvent;
        // Iterator through the list of listeners
        Iterator<SIPTransactionEventListener> listenerIterator;
        // Next listener in the list
        SIPTransactionEventListener nextListener;

        // Create the error event
        newErrorEvent = new SIPTransactionErrorEvent(this, errorEventID);

        // Loop through all listeners of this transaction
        synchronized (eventListeners) {
            listenerIterator = eventListeners.iterator();
            while (listenerIterator.hasNext()) {
                // Send the event to the next listener
                nextListener = (SIPTransactionEventListener) listenerIterator
                        .next();
                nextListener.transactionErrorEvent(newErrorEvent);
            }
        }
        // Clear the event listeners after propagating the error.
        // Retransmit notifications are just an alert to the
        // application (they are not an error).
        if (errorEventID != SIPTransactionErrorEvent.TIMEOUT_RETRANSMIT) {
            eventListeners.clear();

            // Errors always terminate a transaction
            this.setState(TransactionState.TERMINATED);

            if (this instanceof SIPServerTransaction && this.isByeTransaction()
                    && this.getDialog() != null)
                ((SIPDialog) this.getDialog())
                        .setState(SIPDialog.TERMINATED_STATE);
        }
    }

    /**
     * A shortcut way of telling if we are a server transaction.
     */
    protected boolean isServerTransaction() {
        return this instanceof SIPServerTransaction;
    }

    /**
     * Gets the dialog object of this Transaction object. This object returns
     * null if no dialog exists. A dialog only exists for a transaction when a
     * session is setup between a User Agent Client and a User Agent Server,
     * either by a 1xx Provisional Response for an early dialog or a 200OK
     * Response for a committed dialog.
     *
     * @return the Dialog Object of this Transaction object.
     * @see Dialog
     */
    public abstract Dialog getDialog();

    /**
     * set the dialog object.
     *
     * @param sipDialog --
     *            the dialog to set.
     * @param dialogId --
     *            the dialog id ot associate with the dialog.s
     */
    public abstract void setDialog(SIPDialog sipDialog, String dialogId);

    /**
     * Returns the current value of the retransmit timer in milliseconds used to
     * retransmit messages over unreliable transports.
     *
     * @return the integer value of the retransmit timer in milliseconds.
     */
    public int getRetransmitTimer() {
        return SIPTransactionStack.BASE_TIMER_INTERVAL;
    }

    /**
     * Get the host to assign for an outgoing Request via header.
     */
    public String getViaHost() {
        return this.getViaHeader().getHost();

    }

    /**
     * Get the last response. This is used internally by the implementation.
     * Dont rely on it.
     *
     * @return the last response received (for client transactions) or sent (for
     *         server transactions).
     */
    public SIPResponse getLastResponse() {
        return this.lastResponse;
    }

    /**
     * Get the JAIN interface response
     */
    public Response getResponse() {
        return (Response) this.lastResponse;
    }

    /**
     * Get the transaction Id.
     */
    public String getTransactionId() {
        return this.transactionId;
    }

    /**
     * Hashcode method for fast hashtable lookup.
     */
    public int hashCode() {
        if (this.transactionId == null)
            return -1;
        else
            return this.transactionId.hashCode();
    }

    /**
     * Get the port to assign for the via header of an outgoing message.
     */
    public int getViaPort() {
        return this.getViaHeader().getPort();
    }

    /**
     * A method that can be used to test if an incoming request belongs to this
     * transction. This does not take the transaction state into account when
     * doing the check otherwise it is identical to isMessagePartOfTransaction.
     * This is useful for checking if a CANCEL belongs to this transaction.
     *
     * @param requestToTest
     *            is the request to test.
     * @return true if the the request belongs to the transaction.
     *
     */
    public boolean doesCancelMatchTransaction(SIPRequest requestToTest) {

        // List of Via headers in the message to test
        ViaList viaHeaders;
        // Topmost Via header in the list
        Via topViaHeader;
        // Branch code in the topmost Via header
        String messageBranch;
        // Flags whether the select message is part of this transaction
        boolean transactionMatches;

        transactionMatches = false;

        if (this.getOriginalRequest() == null
                || this.getOriginalRequest().getMethod().equals(Request.CANCEL))
            return false;
        // Get the topmost Via header and its branch parameter
        viaHeaders = requestToTest.getViaHeaders();
        if (viaHeaders != null) {

            topViaHeader = (Via) viaHeaders.getFirst();
            messageBranch = topViaHeader.getBranch();
            if (messageBranch != null) {

                // If the branch parameter exists but
                // does not start with the magic cookie,
                if (!messageBranch.toLowerCase().startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) {

                    // Flags this as old
                    // (RFC2543-compatible) client
                    // version
                    messageBranch = null;

                }

            }

            // If a new branch parameter exists,
            if (messageBranch != null && this.getBranch() != null) {

                // If the branch equals the branch in
                // this message,
                if (getBranch().equalsIgnoreCase(messageBranch)
                        && topViaHeader.getSentBy().equals(
                                ((Via) getOriginalRequest().getViaHeaders()
                                        .getFirst()).getSentBy())) {
                    transactionMatches = true;
                    if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                        sipStack.getStackLogger().logDebug("returning  true");
                }

            } else {
                // If this is an RFC2543-compliant message,
                // If RequestURI, To tag, From tag,
                // CallID, CSeq number, and top Via
                // headers are the same,
                if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                    sipStack.getStackLogger().logDebug("testing against "
                            + getOriginalRequest());

                if (getOriginalRequest().getRequestURI().equals(
                        requestToTest.getRequestURI())
                        && getOriginalRequest().getTo().equals(
                                requestToTest.getTo())
                        && getOriginalRequest().getFrom().equals(
                                requestToTest.getFrom())
                        && getOriginalRequest().getCallId().getCallId().equals(
                                requestToTest.getCallId().getCallId())
                        && getOriginalRequest().getCSeq().getSeqNumber() == requestToTest
                                .getCSeq().getSeqNumber()
                        && topViaHeader.equals(getOriginalRequest()
                                .getViaHeaders().getFirst())) {

                    transactionMatches = true;
                }

            }

        }

        // JvB: Need to pass the CANCEL to the listener! Retransmitted INVITEs
        // set it to false
        if (transactionMatches) {
            this.setPassToListener();
        }
        return transactionMatches;
    }

    /**
     * Sets the value of the retransmit timer to the newly supplied timer value.
     * The retransmit timer is expressed in milliseconds and its default value
     * is 500ms. This method allows the application to change the transaction
     * retransmit behavior for different networks. Take the gateway proxy as an
     * example. The internal intranet is likely to be reatively uncongested and
     * the endpoints will be relatively close. The external network is the
     * general Internet. This functionality allows different retransmit times
     * for either side.
     *
     * @param retransmitTimer -
     *            the new integer value of the retransmit timer in milliseconds.
     */
    public void setRetransmitTimer(int retransmitTimer) {

        if (retransmitTimer <= 0)
            throw new IllegalArgumentException(
                    "Retransmit timer must be positive!");
        if (this.transactionTimerStarted.get())
            throw new IllegalStateException(
                    "Transaction timer is already started");
        BASE_TIMER_INTERVAL = retransmitTimer;
        // Commented out for Issue 303 since those timers are configured separately now 
//      T4 = 5000 / BASE_TIMER_INTERVAL;
//      T2 = 4000 / BASE_TIMER_INTERVAL;
//      TIMER_D = 32000 / BASE_TIMER_INTERVAL;

    }

    /**
     * Close the encapsulated channel.
     */
    public void close() {
        this.encapsulatedChannel.close();
        if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            sipStack.getStackLogger().logDebug("Closing " + this.encapsulatedChannel);

    }

    public boolean isSecure() {
        return encapsulatedChannel.isSecure();
    }

    public MessageProcessor getMessageProcessor() {
        return this.encapsulatedChannel.getMessageProcessor();
    }

    /**
     * Set the application data pointer. This is un-interpreted by the stack.
     * This is provided as a conveniant way of keeping book-keeping data for
     * applications. Note that null clears the application data pointer
     * (releases it).
     *
     * @param applicationData --
     *            application data pointer to set. null clears the applicationd
     *            data pointer.
     *
     */

    public void setApplicationData(Object applicationData) {
        this.applicationData = applicationData;
    }

    /**
     * Get the application data associated with this transaction.
     *
     * @return stored application data.
     */
    public Object getApplicationData() {
        return this.applicationData;
    }

    /**
     * Set the encapsuated channel. The peer inet address and port are set equal
     * to the message channel.
     */
    public void setEncapsulatedChannel(MessageChannel messageChannel) {
        this.encapsulatedChannel = messageChannel;
        if ( this instanceof SIPClientTransaction ) {
          this.encapsulatedChannel.setEncapsulatedClientTransaction((SIPClientTransaction) this);
        }
        this.peerInetAddress = messageChannel.getPeerInetAddress();
        this.peerPort = messageChannel.getPeerPort();
    }

    /**
     * Return the SipProvider for which the transaction is assigned.
     *
     * @return the SipProvider for the transaction.
     */
    public SipProviderImpl getSipProvider() {

        return this.getMessageProcessor().getListeningPoint().getProvider();
    }

    /**
     * Raise an IO Exception event - this is used for reporting asynchronous IO
     * Exceptions that are attributable to this transaction.
     *
     */
    public void raiseIOExceptionEvent() {
        setState(TransactionState.TERMINATED);
        String host = getPeerAddress();
        int port = getPeerPort();
        String transport = getTransport();
        IOExceptionEvent exceptionEvent = new IOExceptionEvent(this, host,
                port, transport);
        getSipProvider().handleEvent(exceptionEvent, this);
    }

    /**
     * A given tx can process only a single outstanding event at a time. This
     * semaphore gaurds re-entrancy to the transaction.
     *
     */
    public boolean acquireSem() {
        boolean retval = false;
        try {
            if (sipStack.getStackLogger().isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                sipStack.getStackLogger().logDebug("acquireSem [[[[" + this);
                sipStack.getStackLogger().logStackTrace();
            }
            if ( this.sipStack.maxListenerResponseTime == -1 ) {
                this.semaphore.acquire();
                retval = true;
            } else {
                retval = this.semaphore.tryAcquire(this.sipStack.maxListenerResponseTime, TimeUnit.SECONDS);
            }
            if ( sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                sipStack.getStackLogger().logDebug(
                    "acquireSem() returning : " + retval);
            return retval;
        } catch (Exception ex) {
            sipStack.getStackLogger().logError("Unexpected exception acquiring sem",
                    ex);
            InternalErrorHandler.handleException(ex);
            return false;
        } finally {
            this.isSemaphoreAquired = retval;
        }

    }

    /**
     * Release the transaction semaphore.
     *
     */
    public void releaseSem() {
        try {

            this.toListener = false;
            this.semRelease();

        } catch (Exception ex) {
            sipStack.getStackLogger().logError("Unexpected exception releasing sem",
                    ex);

        }

    }

    protected void semRelease() {
        try {
            if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                sipStack.getStackLogger().logDebug("semRelease ]]]]" + this);
                sipStack.getStackLogger().logStackTrace();
            }
            this.isSemaphoreAquired = false;
            this.semaphore.release();

        } catch (Exception ex) {
            sipStack.getStackLogger().logError("Unexpected exception releasing sem",
                    ex);

        }
    }

    /**
     * Set true to pass the request up to the listener. False otherwise.
     *
     */

    public boolean passToListener() {
        return toListener;
    }

    /**
     * Set the passToListener flag to true.
     */
    public void setPassToListener() {
        if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            sipStack.getStackLogger().logDebug("setPassToListener()");
        }
        this.toListener = true;

    }

    /**
     * Flag to test if the terminated event is delivered.
     *
     * @return
     */
    protected synchronized boolean testAndSetTransactionTerminatedEvent() {
        boolean retval = !this.terminatedEventDelivered;
        this.terminatedEventDelivered = true;
        return retval;
    }
   
    public String getCipherSuite() throws UnsupportedOperationException {
        if (this.getMessageChannel() instanceof TLSMessageChannel ) {
            if (  ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener() == null )
                return null;
            else if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent() == null)
                return null;
            else return ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent().getCipherSuite();
        } else throw new UnsupportedOperationException("Not a TLS channel");

    }

   
    public java.security.cert.Certificate[] getLocalCertificates() throws UnsupportedOperationException {
         if (this.getMessageChannel() instanceof TLSMessageChannel ) {
            if (  ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener() == null )
                return null;
            else if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent() == null)
                return null;
            else return ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent().getLocalCertificates();
        } else throw new UnsupportedOperationException("Not a TLS channel");
    }

   
    public java.security.cert.Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
        if (this.getMessageChannel() instanceof TLSMessageChannel ) {
            if (  ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener() == null )
                return null;
            else if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent() == null)
                return null;
            else return ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent().getPeerCertificates();
        } else throw new UnsupportedOperationException("Not a TLS channel");

    }

    /**
     * Extract identities from certificates exchanged over TLS, based on guidelines
     * from draft-ietf-sip-domain-certs-04.
     * @return list of authenticated identities
     */
    public List<String> extractCertIdentities() throws SSLPeerUnverifiedException {
        if (this.getMessageChannel() instanceof TLSMessageChannel) {
            List<String> certIdentities = new ArrayList<String>();
            Certificate[] certs = getPeerCertificates();
            if (certs == null) {
                if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                    sipStack.getStackLogger().logDebug("No certificates available");
                }
                return certIdentities;
            }
            for (Certificate cert : certs) {
                X509Certificate x509cert = (X509Certificate) cert;
                Collection<List< ? >> subjAltNames = null;
                try {
                    subjAltNames = x509cert.getSubjectAlternativeNames();
                } catch (CertificateParsingException ex) {
                    if (sipStack.isLoggingEnabled()) {
                        sipStack.getStackLogger().logError("Error parsing TLS certificate", ex);
                    }
                }
                // subjAltName types are defined in rfc2459
                final Integer dnsNameType = 2;
                final Integer uriNameType = 6;
                if (subjAltNames != null) {
                    if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                        sipStack.getStackLogger().logDebug("found subjAltNames: " + subjAltNames);
                    }
                    // First look for a URI in the subjectAltName field
                    // as per draft-ietf-sip-domain-certs-04
                    for (List< ? > altName : subjAltNames) {
                        // 0th position is the alt name type
                        // 1st position is the alt name data
                        if (altName.get(0).equals(uriNameType)) {
                            SipURI altNameUri;
                            try {
                                altNameUri = new AddressFactoryImpl().createSipURI((String) altName.get(1));
                                String altHostName = altNameUri.getHost();
                                if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                                    sipStack.getStackLogger().logDebug(
                                        "found uri " + altName.get(1) + ", hostName " + altHostName);
                                }
                                certIdentities.add(altHostName);
                            } catch (ParseException e) {
                                if (sipStack.isLoggingEnabled()) {
                                    sipStack.getStackLogger().logError(
                                        "certificate contains invalid uri: " + altName.get(1));
                                }
                            }
                        }

                    }
                    // DNS An implementation MUST accept a domain name system
                    // identifier as a SIP domain identity if and only if no other
                    // identity is found that matches the "sip" URI type described
                    // above.
                    if (certIdentities.isEmpty()) {
                        for (List< ? > altName : subjAltNames) {
                            if (altName.get(0).equals(dnsNameType)) {
                                if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                                    sipStack.getStackLogger().logDebug("found dns " + altName.get(1));
                                }
                                certIdentities.add(altName.get(1).toString());
                            }
                        }
                    }
                } else {
                    // If and only if the subjectAltName does not appear in the
                    // certificate, the implementation MAY examine the CN field of the
                    // certificate. If a valid DNS name is found there, the
                    // implementation MAY accept this value as a SIP domain identity.
                    String dname = x509cert.getSubjectDN().getName();
                    String cname = "";
                    try {
                        Pattern EXTRACT_CN = Pattern.compile(".*CN\\s*=\\s*([\\w*\\.]+).*");
                        Matcher matcher = EXTRACT_CN.matcher(dname);
                        if (matcher.matches()) {
                            cname = matcher.group(1);
                            if (sipStack.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                                sipStack.getStackLogger().logDebug("found CN: " + cname + " from DN: " + dname);
                            }
                            certIdentities.add(cname);
                        }
                    } catch (Exception ex) {
                        if (sipStack.isLoggingEnabled()) {
                            sipStack.getStackLogger().logError("exception while extracting CN", ex);
                        }
                    }
                }
            }
            return certIdentities;
        } else
            throw new UnsupportedOperationException("Not a TLS channel");
    }


    /**
     * Start the timer that runs the transaction state machine.
     *
     */

    protected abstract void startTransactionTimer();

    /**
     * Tests a message to see if it is part of this transaction.
     *
     * @return True if the message is part of this transaction, false if not.
     */
    public abstract boolean isMessagePartOfTransaction(SIPMessage messageToTest);

    /**
     * This method is called when this transaction's retransmission timer has
     * fired.
     */
    protected abstract void fireRetransmissionTimer();

    /**
     * This method is called when this transaction's timeout timer has fired.
     */
    protected abstract void fireTimeoutTimer();   


    /*
     * (non-Javadoc)
     * @see gov.nist.javax.sip.TransactionExt#getTimerD()
     */
    public int getTimerD() {     
        return TIMER_D;
    }
  
    /*
     * (non-Javadoc)
     * @see gov.nist.javax.sip.TransactionExt#getTimerT2()
     */
    public int getTimerT2() {
        return T2;
    }

    /*
     * (non-Javadoc)
     * @see gov.nist.javax.sip.TransactionExt#getTimerT4()
     */
    public int getTimerT4() {
        return T4;
    }

    /*
     * (non-Javadoc)
     * @see gov.nist.javax.sip.TransactionExt#setTimerD(int)
     */
    public void setTimerD(int interval) {
      if(interval < 32000) {
        throw new IllegalArgumentException("To be RFC 3261 compliant, the value of Timer D should be at least 32s");
      }
        TIMER_D = interval / BASE_TIMER_INTERVAL;
    }
   
    /*
     * (non-Javadoc)
     * @see gov.nist.javax.sip.TransactionExt#setTimerT2(int)
     */
    public void setTimerT2(int interval) {
        T2 = interval / BASE_TIMER_INTERVAL;
    }

    /*
     * (non-Javadoc)
     * @see gov.nist.javax.sip.TransactionExt#setTimerT4(int)
     */
    public void setTimerT4(int interval) {
        T4 = interval / BASE_TIMER_INTERVAL;
        TIMER_I = T4;
        TIMER_K = T4;
    }
}
TOP

Related Classes of gov.nist.javax.sip.stack.SIPTransaction$LingerTimer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.