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

org.ice4j.ice.CandidatePair Maven / Gradle / Ivy

There is a newer version: 3.2-4-g1373788
Show newest version
/*
 * ice4j, the OpenSource Java Solution for NAT and Firewall Traversal.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ice4j.ice;

import java.net.*;
import java.util.*;

import org.ice4j.socket.*;
import org.ice4j.stack.*;

/**
 * CandidatePairs map local to remote Candidates so that they
 * could be added to check lists. Connectivity in ICE is always verified by
 * pairs: i.e. STUN packets are sent from the local candidate of a pair to the
 * remote candidate of a pair. To see which pairs work, an agent schedules a
 * series of ConnectivityChecks. Each check is a STUN request/response
 * transaction that the client will perform on a particular candidate pair by
 * sending a STUN request from the local candidate to the remote candidate.
 *
 * @author Emil Ivov
 * @author Lyubomir Marinov
 * @author Boris Grozev
 */
public class CandidatePair
    implements Comparable
{
    /**
     * The value of the consentFreshness property of
     * CandidatePair which indicates that the time in milliseconds of
     * the latest consent freshness confirmation is unknown.
     */
    public static final long CONSENT_FRESHNESS_UNKNOWN = -1;

    /**
     * The value of Math.pow(2, 32) calculated once for the purposes of
     * optimizing performance.
     */
    private static final long MATH_POW_2_32 = 1L << 32;

    /**
     * A Comparator using the compareTo method of the
     * CandidatePair.
     */
    public static final PairComparator comparator = new PairComparator();

    /**
     * The local candidate of this pair.
     */
    private LocalCandidate localCandidate;

    /**
     * The remote candidate of this pair.
     */
    private RemoteCandidate remoteCandidate;

    /**
     * Priority of the candidate-pair
     */
    private long priority;

    /**
     * A flag indicating whether we have seen an incoming check request that
     * contained the USE-CANDIDATE attribute for this pair.
     */
    private boolean useCandidate = false;

    /**
     * A flag indicating whether we have sent a check request that
     * contained the USE-CANDIDATE attribute for this pair. It is used in
     * GTalk compatibility mode as it lacks USE-CANDIDATE.
     */
    private boolean useCandidateSent = false;

    /**
     * Indicates whether this CandidatePair is on any of this agent's
     * valid pair lists.
     */
    private boolean isValid = false;

    /**
     * If a valid candidate pair has its nominated flag set, it means that it
     * may be selected by ICE for sending and receiving media.
     */
    private boolean isNominated = false;

    /**
     * Each candidate pair has a state that is assigned once the check list
     * for each media stream has been computed. The ICE RFC defines five
     * potential values that the state can have and they are all represented
     * in the CandidatePairState enumeration. The ICE spec stipulates
     * that the first step of the state initialization process is: The agent
     * sets all of the pairs in each check list to the Frozen state, and hence
     * our default state.
     */
    private CandidatePairState state = CandidatePairState.FROZEN;

    /**
     * The {@link TransactionID} of the client transaction for a connectivity
     * check over this pair in case it is in the
     * {@link CandidatePairState#IN_PROGRESS} state.
     */
    private TransactionID connCheckTranID = null;

    /**
     * The time in milliseconds of the latest consent freshness confirmation.
     */
    private long consentFreshness = CONSENT_FRESHNESS_UNKNOWN;

    /**
     * Creates a CandidatePair instance mapping localCandidate
     * to remoteCandidate.
     *
     * @param localCandidate the local candidate of the pair.
     * @param remoteCandidate the remote candidate of the pair.
     */
    public CandidatePair(LocalCandidate localCandidate,
                         RemoteCandidate remoteCandidate)
    {
        this.localCandidate = localCandidate;
        this.remoteCandidate = remoteCandidate;

        computePriority();
    }

    /**
     * Returns the foundation of this CandidatePair. The foundation
     * of a CandidatePair is just the concatenation of the foundations
     * of its two candidates. Initially, only the candidate pairs with unique
     * foundations are tested. The other candidate pairs are marked "frozen".
     * When the connectivity checks for a candidate pair succeed, the other
     * candidate pairs with the same foundation are unfrozen. This avoids
     * repeated checking of components which are superficially more attractive
     * but in fact are likely to fail.
     *
     * @return the foundation of this candidate pair, which is a concatenation
     * of the foundations of the remote and local candidates.
     */
    public String getFoundation()
    {
        return localCandidate.getFoundation()
            + remoteCandidate.getFoundation();
    }

    /**
     * Returns the LocalCandidate of this CandidatePair.
     *
     * @return the local Candidate of this CandidatePair.
     */
    public LocalCandidate getLocalCandidate()
    {
        return localCandidate;
    }

    /**
     * Sets the LocalCandidate of this CandidatePair.
     *
     * @param localCnd the local Candidate of this
     * CandidatePair.
     */
    protected void setLocalCandidate(LocalCandidate localCnd)
    {
        this.localCandidate = localCnd;
    }

    /**
     * Returns the remote candidate of this CandidatePair.
     *
     * @return the remote Candidate of this CandidatePair.
     */
    public RemoteCandidate getRemoteCandidate()
    {
        return remoteCandidate;
    }

    /**
     * Sets the RemoteCandidate of this CandidatePair.
     *
     * @param remoteCnd the local Candidate of this
     * CandidatePair.
     */
    protected void setRemoteCandidate(RemoteCandidate remoteCnd)
    {
        this.remoteCandidate = remoteCnd;
    }

    /**
     * Returns the state of this CandidatePair. Each candidate pair has
     * a state that is assigned once the check list for each media stream has
     * been computed. The ICE RFC defines five potential values that the state
     * can have. They are represented here with the CandidatePairState
     * enumeration.
     *
     * @return the CandidatePairState that this candidate pair is
     * currently in.
     */
    public CandidatePairState getState()
    {
        return state;
    }

    /**
     * Sets the CandidatePairState of this pair to
     * {@link CandidatePairState#FAILED}. This method should only be called by
     * the ICE agent, during the execution of the ICE procedures.
     */
    public void setStateFailed()
    {
        setState(CandidatePairState.FAILED, null);
    }

    /**
     * Sets the CandidatePairState of this pair to
     * {@link CandidatePairState#FROZEN}. This method should only be called by
     * the ICE agent, during the execution of the ICE procedures.
     */
    public void setStateFrozen()
    {
        setState(CandidatePairState.FROZEN, null);
    }

    /**
     * Sets the CandidatePairState of this pair to
     * {@link CandidatePairState#FROZEN}. This method should only be called by
     * the ICE agent, during the execution of the ICE procedures.
     *
     * @param tranID the {@link TransactionID} that we are using for the
     * connectivity check in case we are entering the In-Progress
     * state and null otherwise.
     */
    public void setStateInProgress(TransactionID tranID)
    {
        setState(CandidatePairState.IN_PROGRESS, tranID);
    }

    /**
     * Sets the CandidatePairState of this pair to
     * {@link CandidatePairState#SUCCEEDED}. This method should only be called
     * by the ICE agent, during the execution of the ICE procedures.
     */
    public void setStateSucceeded()
    {
        setState(CandidatePairState.SUCCEEDED, null);
    }

    /**
     * Sets the CandidatePairState of this pair to
     * {@link CandidatePairState#WAITING}. This method should only be called by
     * the ICE agent, during the execution of the ICE procedures.
     */
    public void setStateWaiting()
    {
        setState(CandidatePairState.WAITING, null);
    }

    /**
     * Sets the CandidatePairState of this pair to state. This
     * method should only be called by the ice agent, during the execution of
     * the ICE procedures. Note that passing a null transaction for the
     * {@link CandidatePairState#IN_PROGRESS} or a non-null for any
     * other state would cause an {@link IllegalArgumentException} to be thrown.
     *
     * @param newState the state that this candidate pair is to enter.
     * @param tranID the {@link TransactionID} that we are using for the
     * connectivity check in case we are entering the In-Progress
     * state and null otherwise.
     *
     * @throws IllegalArgumentException if state is {@link CandidatePairState
     * #IN_PROGRESS} and tranID is null.
     */
    private synchronized void setState(CandidatePairState newState,
                                       TransactionID      tranID)
        throws IllegalArgumentException
    {
        CandidatePairState oldState = this.state;

        this.state = newState;

        if (newState == CandidatePairState.IN_PROGRESS)
        {
            if (tranID == null)
            {
                throw new IllegalArgumentException(
                        "Putting a pair into the In-Progress state MUST be"
                            + " accompanied with the TransactionID of the"
                            + " connectivity check.");
            }
        }
        else
        {
            if (tranID != null)
            {
                throw new IllegalArgumentException(
                        "How could you have a transaction for a pair that's not"
                            + " in the In-Progress state?");
            }
        }
        this.connCheckTranID = tranID;

        getParentComponent().getParentStream().firePairPropertyChange(
                this,
                IceMediaStream.PROPERTY_PAIR_STATE_CHANGED,
                oldState,
                newState);
    }

    /**
     * Determines whether this candidate pair is frozen or not. Initially, only
     * the candidate pairs with unique foundations are tested. The other
     * candidate pairs are marked "frozen". When the connectivity checks for a
     * candidate pair succeed, the other candidate pairs with the same
     * foundation are unfrozen.
     *
     * @return true if this candidate pair is frozen and false otherwise.
     */
    public boolean isFrozen()
    {
        return this.getState().equals(CandidatePairState.FROZEN);
    }

    /**
     * Returns the candidate in this pair that belongs to the controlling agent.
     *
     * @return a reference to the Candidate instance that comes from
     * the controlling agent.
     */
    public Candidate getControllingAgentCandidate()
    {
        return (getLocalCandidate().getParentComponent().getParentStream()
                        .getParentAgent().isControlling())
                    ? getLocalCandidate()
                    : getRemoteCandidate();
    }

    /**
     * Returns the candidate in this pair that belongs to the controlled agent.
     *
     * @return a reference to the Candidate instance that comes from
     * the controlled agent.
     */
    public Candidate getControlledAgentCandidate()
    {
        return (getLocalCandidate().getParentComponent().getParentStream()
                        .getParentAgent().isControlling())
                    ? getRemoteCandidate()
                    : getLocalCandidate();
    }


    /**
     * A candidate pair priority is computed the following way:
* Let G be the priority for the candidate provided by the controlling * agent. Let D be the priority for the candidate provided by the * controlled agent. The priority for a pair is computed as: *

* pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) *

* This formula ensures a unique priority for each pair. Once the priority * is assigned, the agent sorts the candidate pairs in decreasing order of * priority. If two pairs have identical priority, the ordering amongst * them is arbitrary. */ protected void computePriority() { // Use g and d as local and remote candidate priority names to fit the // definition in the RFC. long g = getControllingAgentCandidate().getPriority(); long d = getControlledAgentCandidate().getPriority(); long min, max, expr; if (g > d) { min = d; max = g; expr = 1L; } else { min = g; max = d; expr = 0L; } this.priority = MATH_POW_2_32 * min + 2 * max + expr; } /** * Returns the priority of this pair. * * @return the priority of this pair. */ public long getPriority() { return priority; } /** * Compares this CandidatePair with the specified object for order. * Returns a negative integer, zero, or a positive integer as this * CandidatePair's priority is greater than, equal to, or less than * the one of the specified object thus insuring that higher priority pairs * will come first.

* * @param candidatePair the Object to be compared. * @return a negative integer, zero, or a positive integer as this * CandidatePair's priority is greater than, equal to, or less than * the one of the specified object. * * @throws ClassCastException if the specified object's type prevents it * from being compared to this Object. */ public int compareTo(CandidatePair candidatePair) { long thisPri = getPriority(); long otherPri = candidatePair.getPriority(); return (thisPri < otherPri) ? 1 : (thisPri == otherPri) ? 0 : -1; } /** * Compares this CandidatePair to obj and returns * true if pairs have equal local and equal remote candidates and * false otherwise. * * @param obj the Object that we'd like to compare this pair to. * @return true if pairs have equal local and equal remote * candidates and false otherwise. */ @Override public boolean equals(Object obj) { if (! (obj instanceof CandidatePair)) return false; CandidatePair candidatePair = (CandidatePair) obj; // XXX DO NOT change this method to also depend on other pair properties // because ConnectivityCheckClient counts on it only using the // candidates for comparisons. return localCandidate.equals(candidatePair.localCandidate) && remoteCandidate.equals(candidatePair.remoteCandidate); } /** * {@inheritDoc} */ @Override public int hashCode() { // Even if the following hashCode algorithm has drawbacks because of it // simplicity, it is better than nothing because at least it allows // CandidatePair to be used as a HashMap key. // XXX While localCandidate is not final, the parentComponent is // supposedly effectively final. return getLocalCandidate().getParentComponent().hashCode(); } /** * Returns a String representation of this CandidatePair. * * @return a String representation of the object. */ @Override public String toString() { return "CandidatePair (State=" + getState() + " Priority=" + getPriority() + "):\n\tLocalCandidate=" + getLocalCandidate() + "\n\tRemoteCandidate=" + getRemoteCandidate(); } /** * Returns a String representation of this CandidatePair, with the remote * candidate IP address redacted if redaction is enabled. * * @return a String representation of the object. */ public String toRedactedString() { return "CandidatePair (State=" + getState() + " Priority=" + getPriority() + "):\n\tLocalCandidate=" + getLocalCandidate() + "\n\tRemoteCandidate=" + getRemoteCandidate().toRedactedString(); } /** * Returns a short String representation of this CandidatePair. * * @return a short String representation of the object. */ public String toShortString() { return getLocalCandidate().toShortString() + " -> " + getRemoteCandidate().toShortString() + " (" + getParentComponent().toShortString() + ")"; } /** * Returns a short String representation of this CandidatePair, * with the remote candidate IP address redacted if redaction is enabled. * * @return a redacted short String representation of the object. */ public String toRedactedShortString() { return getLocalCandidate().toShortString() + " -> " + getRemoteCandidate().toRedactedShortString() + " (" + getParentComponent().toShortString() + ")"; } /** * A Comparator using the compareTo method of the * CandidatePair */ public static class PairComparator implements Comparator { /** * Compares pair1 and pair2 for order. Returns a * negative integer, zero, or a positive integer as pair1's * priority is greater than, equal to, or less than the one of the * pair2, thus insuring that higher priority pairs will come first. * * @param pair1 the first CandidatePair to be compared. * @param pair2 the second CandidatePair to be compared. * * @return a negative integer, zero, or a positive integer as the first * pair's priority priority is greater than, equal to, or less than * the one of the second pair. * * @throws ClassCastException if the specified object's type prevents it * from being compared to this Object. */ public int compare(CandidatePair pair1, CandidatePair pair2) { return pair1.compareTo(pair2); } /** * Indicates whether some other object is "equal to" to this * Comparator. This method must obey the general contract of * Object.equals(Object). Additionally, this method can return * true only if the specified Object is also a * comparator and it imposes the same ordering as this comparator. Thus, * comp1.equals(comp2) implies that * sgn(comp1.compare(o1,o2))==sgn(comp2.compare(o1, o2)) for * every object reference o1 and o2.

* * Note that it is always safe not to override * Object.equals(Object). However, overriding this method may, * in some cases, improve performance by allowing programs to determine * that two distinct Comparators impose the same order. * * @param obj the reference object with which to compare. * @return true only if the specified object is also * a comparator and it imposes the same ordering as this * comparator. * @see java.lang.Object#equals(java.lang.Object) * @see java.lang.Object#hashCode() */ @Override public boolean equals(Object obj) { return obj instanceof PairComparator; } } /** * Returns the Component that this pair belongs to. * * @return the Component that this pair belongs to. */ public Component getParentComponent() { return getLocalCandidate().getParentComponent(); } /** * Returns the {@link TransactionID} used in the connectivity check * associated with this {@link CandidatePair} when it's in the * {@link CandidatePairState#IN_PROGRESS} or null if it's in * any other state. * * @return the {@link TransactionID} used in the connectivity check * associated with this {@link CandidatePair} when it's in the * {@link CandidatePairState#IN_PROGRESS} or null if it's in * any other state. */ public TransactionID getConnectivityCheckTransaction() { return connCheckTranID; } /** * Raises the useCandidateSent flag for this pair. */ public void setUseCandidateSent() { this.useCandidateSent = true; } /** * Returns true if someone has previously raised this pair's * useCandidateSent flag and false otherwise. * * @return true if someone has previously raised this pair's * useCandidate flag and false otherwise. */ public boolean useCandidateSent() { return useCandidateSent; } /** * Raises the useCandidate flag for this pair. */ public void setUseCandidateReceived() { this.useCandidate = true; } /** * Returns true if someone has previously raised this pair's * useCandidate flag and false otherwise. * * @return true if someone has previously raised this pair's * useCandidate flag and false otherwise. */ public boolean useCandidateReceived() { return useCandidate; } /** * Sets this pair's nominated flag to true. If a valid candidate * pair has its nominated flag set, it means that it may be selected by ICE * for sending and receiving media. */ public void nominate() { this.isNominated = true; getParentComponent().getParentStream().firePairPropertyChange( this, IceMediaStream.PROPERTY_PAIR_NOMINATED, /* oldValue */ false, /* newValue */ true); } /** * Returns the value of this pair's nominated flag. If a valid candidate * pair has its nominated flag set, it means that it may be selected by ICE * for sending and receiving media. * * @return true if this pair has already been nominated for * selection and false otherwise. */ public boolean isNominated() { return this.isNominated; } /** * Returns true if this pair has been confirmed by a connectivity * check response and false otherwise. * * @return true if this pair has been confirmed by a connectivity * check response and false otherwise. */ public boolean isValid() { return isValid; } /** * Marks this pair as valid. Should only be used internally. */ protected void validate() { this.isValid = true; getParentComponent().getParentStream().firePairPropertyChange( this, IceMediaStream.PROPERTY_PAIR_VALIDATED, false, true); } /** * Gets the time in milliseconds of the latest consent freshness * confirmation. * * @return the time in milliseconds of the latest consent freshness * confirmation */ public long getConsentFreshness() { return consentFreshness; } /** * Sets the time in milliseconds of the latest consent freshness * confirmation to now. */ void setConsentFreshness() { setConsentFreshness(System.currentTimeMillis()); } /** * Sets the time in milliseconds of the latest consent freshness * confirmation to a specific time. * * @param consentFreshness the time in milliseconds of the latest consent * freshness to be set on this CandidatePair */ void setConsentFreshness(long consentFreshness) { if (this.consentFreshness != consentFreshness) { long oldValue = this.consentFreshness; this.consentFreshness = consentFreshness; long newValue = this.consentFreshness; getParentComponent().getParentStream().firePairPropertyChange( this, IceMediaStream.PROPERTY_PAIR_CONSENT_FRESHNESS_CHANGED, oldValue, newValue); } } /** * Returns the UDP DatagramSocket (if any) for this * CandidatePair. * @return the UDP DatagramSocket (if any) for this * CandidatePair. */ @Deprecated public DatagramSocket getDatagramSocket() { IceSocketWrapper wrapper = getIceSocketWrapper(); return wrapper == null ? null : wrapper.getUDPSocket(); } /** * Returns the TCP Socket (if any) for this CandidatePair. * @return the TCP Socket (if any) for this CandidatePair. */ @Deprecated public Socket getSocket() { return null; } /** * Returns the IceSocketWrapper for this CandidatePair. * @return the IceSocketWrapper for this CandidatePair. */ @Deprecated public IceSocketWrapper getIceSocketWrapper() { IceSocketWrapper componentSocket = getParentComponent().getSocketWrapper(); // If the merging socket is used, all candidate pairs just refer to the // component. if (componentSocket != null) { return getParentComponent().getSocketWrapper(); } LocalCandidate localCandidate = getLocalCandidate(); if (localCandidate == null) { return null; } LocalCandidate base = localCandidate.getBase(); if (base != null) { localCandidate = base; } RemoteCandidate remoteCandidate = getRemoteCandidate(); if (remoteCandidate != null) { SocketAddress remoteAddress = remoteCandidate.getTransportAddress(); if (remoteAddress != null) { return localCandidate.getCandidateIceSocketWrapper(remoteAddress); } } return localCandidate.getCandidateIceSocketWrapper(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy