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

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

/*
 * 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 org.jitsi.utils.logging2.*;

import java.beans.*;
import java.util.*;

/**
 * Implements ice4j internal nomination strategies.
 *
 * @author Emil Ivov
 */
public class DefaultNominator
    implements PropertyChangeListener
{
    /**
     * The Agent that created us.
     */
    private final Agent parentAgent;

    /**
     * The strategy that this nominator should use to nominate valid pairs.
     */
    private NominationStrategy strategy
        = NominationStrategy.NOMINATE_FIRST_VALID;

    /**
     * Map that will remember association between validated relayed candidate
     * and a timer. It is used with the NOMINATE_FIRST_HIGHEST_VALID strategy.
     */
    private final Map validatedCandidates = new HashMap<>();

    /**
     * The {@link Logger} used by {@link DefaultNominator} instances.
     */
    private Logger logger;

    /**
     * Creates a new instance of this nominator using parentAgent as
     * a reference to the Agent instance that we should use to
     * nominate pairs.
     *
     * @param parentAgent the {@link Agent} that created us.
     */
    public DefaultNominator(Agent parentAgent)
    {
        this.parentAgent = parentAgent;
        logger = parentAgent.getLogger().createChildLogger(this.getClass().getName());
        parentAgent.addStateChangeListener(this);
    }

    /**
     * Tracks changes of state in {@link IceMediaStream}s and {@link
     * CheckList}s.
     *
     * @param ev the event that we should use in case it means we should
     * nominate someone.
     */
    public void propertyChange(PropertyChangeEvent ev)
    {
        String propertyName = ev.getPropertyName();

        if (Agent.PROPERTY_ICE_PROCESSING_STATE.equals(propertyName))
        {
            if (ev.getNewValue() != IceProcessingState.RUNNING)
                return;

            for (IceMediaStream stream : parentAgent.getStreams())
            {
                stream.addPairChangeListener(this);
                stream.getCheckList().addStateChangeListener(this);
            }
        }

        if (!parentAgent.isControlling() //CONTROLLED agents cannot nominate
                || strategy == NominationStrategy.NONE)
        {
            return;
        }

        if (ev.getSource() instanceof CandidatePair)
        {
            // STUN Usage for Consent Freshness is of no concern here.
            if (IceMediaStream.PROPERTY_PAIR_CONSENT_FRESHNESS_CHANGED.equals(propertyName))
            {
                return;
            }

            CandidatePair validPair = (CandidatePair) ev.getSource();
            Component parentComponent = validPair.getParentComponent();
            IceMediaStream parentStream = parentComponent.getParentStream();

            // do not nominate pair if there is currently a nominated pair for
            // the component
            if (parentStream.validListContainsNomineeForComponent(parentComponent))
            {
                logger.debug(() ->
                        "Keep-alive for pair: " + validPair.toRedactedShortString());
                return;
            }
        }

        if (strategy == NominationStrategy.NOMINATE_FIRST_VALID)
            strategyNominateFirstValid(ev);
        else if (strategy == NominationStrategy.NOMINATE_HIGHEST_PRIO)
            strategyNominateHighestPrio(ev);
        else if (strategy
                == NominationStrategy.NOMINATE_FIRST_HOST_OR_REFLEXIVE_VALID)
            strategyNominateFirstHostOrReflexiveValid(ev);
    }

    /**
     * Implements a basic nomination strategy that consists in nominating the
     * first pair that has become valid for a check list.
     *
     * @param evt the {@link PropertyChangeEvent} containing the pair which
     * has been validated.
     */
    private void strategyNominateFirstValid(PropertyChangeEvent evt)
    {
        if (IceMediaStream.PROPERTY_PAIR_VALIDATED
                    .equals(evt.getPropertyName()))
        {
            CandidatePair validPair = (CandidatePair)evt.getSource();

            logger.info("Nominate (first valid): " + validPair.toRedactedShortString()
                + ".");
            parentAgent.nominate(validPair);
        }
    }

    /**
     * Implements a nomination strategy that allows checks for several (or all)
     * pairs in a check list to conclude before nominating the one with the
     * highest priority.
     *
     * @param ev the {@link PropertyChangeEvent} containing the new state and
     * the source {@link CheckList}.
     */
    private void strategyNominateHighestPrio(PropertyChangeEvent ev)
    {
        String pname = ev.getPropertyName();

        if (IceMediaStream.PROPERTY_PAIR_VALIDATED.equals(pname)
                || (IceMediaStream.PROPERTY_PAIR_STATE_CHANGED.equals(pname)
                        && (ev.getNewValue() == CandidatePairState.FAILED)))
        {
            CandidatePair validPair = (CandidatePair) ev.getSource();
            Component parentComponent = validPair.getParentComponent();
            IceMediaStream parentStream = parentComponent.getParentStream();
            CheckList parentCheckList = parentStream.getCheckList();

            if (!parentCheckList.allChecksCompleted())
                return;

            for (Component component : parentStream.getComponents())
            {
                CandidatePair pair = parentStream.getValidPair(component);

                if (pair != null)
                {
                    logger.info(
                            "Nominate (highest priority): "
                                + validPair.toRedactedShortString());
                    parentAgent.nominate(pair);
                }
            }
        }
    }

    /**
     * The {@link NominationStrategy} that this nominator is using when
     * deciding whether or not a valid {@link CandidatePair} is suitable for
     * nomination.
     *
     * @return the {@link NominationStrategy} we are using.
     */
    public NominationStrategy getStrategy()
    {
        return strategy;
    }

    /**
     * The {@link NominationStrategy} that this nominator should use when
     * deciding whether or not a valid {@link CandidatePair} is suitable for
     * nomination.
     *
     * @param strategy the {@link NominationStrategy} we should be using.
     */
    public void setStrategy(NominationStrategy strategy)
    {
        this.strategy = strategy;
    }

    /**
     * Implements a nomination strategy that consists in nominating directly
     * host or server reflexive pair that has become valid for a
     * check list. For relayed pair, a timer is armed to see if no other host or
     * server reflexive pair gets validated prior to timeout, the relayed ones
     * gets nominated.
     *
     * @param evt the {@link PropertyChangeEvent} containing the pair which
     * has been validated.
     */
    private void strategyNominateFirstHostOrReflexiveValid(
            PropertyChangeEvent evt)
    {
        if (IceMediaStream.PROPERTY_PAIR_VALIDATED.equals(evt.getPropertyName()))
        {
            CandidatePair validPair = (CandidatePair) evt.getSource();

            Component component = validPair.getParentComponent();
            LocalCandidate localCandidate = validPair.getLocalCandidate();
            boolean isRelayed
                = (localCandidate instanceof RelayedCandidate)
                    || localCandidate.getType().equals(
                            CandidateType.RELAYED_CANDIDATE)
                    || validPair.getRemoteCandidate().getType().equals(
                            CandidateType.RELAYED_CANDIDATE);
            boolean nominate = false;

            synchronized (validatedCandidates)
            {
                TimerTask task
                    = validatedCandidates.get(component.toShortString());

                if (isRelayed && task == null)
                {
                    /* armed a timer and see if a host or server reflexive pair
                     * gets nominated. Otherwise nominate the relayed candidate
                     * pair
                     */
                    Timer timer = new Timer();
                    task = new RelayedCandidateTask(validPair);

                    logger.info("Wait timeout to nominate relayed candidate");
                    timer.schedule(task, 0);
                    validatedCandidates.put(component.toShortString(), task);
                }
                else if (!isRelayed)
                {
                    // host or server reflexive candidate pair
                    if (task != null)
                    {
                        task.cancel();
                        logger.info(
                                "Found a better candidate pair to nominate for "
                                    + component.toShortString());
                    }

                    logger.info(
                            "Nominate (first highest valid): "
                                + validPair.toRedactedShortString());
                    nominate = true;
                }
            }

            if (nominate)
                parentAgent.nominate(validPair);
        }
    }

    /**
     * TimerTask that will wait a certain amount of time to let other candidate
     * pair to be validated and possibly be better than the relayed candidate.
     *
     * @author Sebastien Vincent
     */
    private class RelayedCandidateTask
        extends TimerTask
        implements PropertyChangeListener
    {
        /**
         * Wait time in milliseconds.
         */
        private static final int WAIT_TIME = 800;

        /**
         * The relayed candidate pair.
         */
        private final CandidatePair pair;

        /**
         * If the task has been cancelled.
         */
        private boolean cancelled = false;

        /**
         * Constructor.
         *
         * @param pair relayed candidate pair
         */
        public RelayedCandidateTask(CandidatePair pair)
        {
            this.pair = pair;
            pair.getParentComponent().getParentStream().getCheckList().
                addChecksListener(this);
        }

        /**
         * Tracks end of checks of the {@link CheckList}.
         *
         * @param evt the event
         */
        public void propertyChange(PropertyChangeEvent evt)
        {
            // Make it clear that PROPERTY_CHECK_LIST_CHECKS is in use here.
            if (!CheckList.PROPERTY_CHECK_LIST_CHECKS.equals(
                        evt.getPropertyName()))
            {
                return;
            }

            // check list has run out of ordinary checks, see if all other
            // candidates are FAILED, in which case we nominate immediately
            // the relayed candidate
            CheckList checkList = (CheckList)evt.getSource();
            boolean allFailed = true;

            synchronized (checkList)
            {
                for (CandidatePair c : checkList)
                {
                    if (c != pair && c.getState() != CandidatePairState.FAILED)
                    {
                        allFailed = false;
                        break;
                    }
                }
            }

            if (allFailed && !pair.isNominated())
            {
                // all other pairs are failed to do not waste time, cancel
                // timer and nominate ourself (the relayed candidate).
                this.cancel();

                logger.info(
                        "Nominate (first highest valid): "
                            + pair.toRedactedShortString());
                parentAgent.nominate(pair);
            }
        }

        /**
         * Cancel task.
         */
        @Override
        public boolean cancel()
        {
            cancelled = true;
            return super.cancel();
        }

        /**
         * Task entry point.
         */
        public void run()
        {
            try
            {
                Thread.sleep(WAIT_TIME);
            }
            catch (InterruptedException e)
            {
                cancelled = true;
            }

            Component component = pair.getParentComponent();

            component.getParentStream().getCheckList().removeChecksListener(
                    this);
            validatedCandidates.remove(component.toShortString());

            if (cancelled)
                return;

            logger.info(
                    "Nominate (first highest valid): " + pair.toRedactedShortString());

            // task has not been cancelled after WAIT_TIME milliseconds so
            // nominate the pair
            parentAgent.nominate(pair);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy