<commit_before>/*
 * 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.jitsi.videobridge;

import java.beans.*;
import java.io.*;
import java.net.*;
import java.util.*;

import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.CandidateType;
import net.java.sip.communicator.service.netaddr.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.*;

import org.ice4j.*;
import org.ice4j.ice.*;
import org.ice4j.ice.harvest.*;
import org.ice4j.socket.*;
import org.jitsi.eventadmin.*;
import org.jitsi.impl.neomedia.transform.dtls.*;
import org.jitsi.service.configuration.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.util.*;
import org.jitsi.util.Logger;
import org.osgi.framework.*;

/**
 * Implements the Jingle ICE-UDP transport.
 *
 * @author <NAME>
 * @author <NAME>
 * @author <NAME>
 */
public class IceUdpTransportManager
    extends TransportManager
{
    /**
     * The name default of the single <tt>IceStream</tt> that this
     * <tt>TransportManager</tt> will create/use.
     */
    private static final String DEFAULT_ICE_STREAM_NAME = "stream";

    /**
     * The name of the property which disables the use of a
     * <tt>TcpHarvester</tt>.
     */
    public static final String DISABLE_TCP_HARVESTER
        = "org.jitsi.videobridge.DISABLE_TCP_HARVESTER";

    /**
     * The name of the property which controls the port number used for
     * <tt>SinglePortUdpHarvester</tt>s.
     */
    public static final String SINGLE_PORT_HARVESTER_PORT
            = "org.jitsi.videobridge.SINGLE_PORT_HARVESTER_PORT";

    /**
     * The default value of the port to be used for
     * {@code SinglePortUdpHarvester}.
     */
    private static final int SINGLE_PORT_DEFAULT_VALUE = 10000;

    /**
     * The {@link Logger} used by the {@link IceUdpTransportManager} class to
     * print debug information. Note that instances should use {@link #logger}
     * instead.
     */
    private static final Logger classLogger
        = Logger.getLogger(IceUdpTransportManager.class);

    /**
     * The default port that the <tt>TcpHarvester</tt> will
     * bind to.
     */
    private static final int TCP_DEFAULT_PORT = 443;

    /**
     * The port on which the <tt>TcpHarvester</tt> will bind to
     * if no port is specifically configured, and binding to
     * <tt>DEFAULT_TCP_PORT</tt> fails (for example, if the process doesn't have
     * the required privileges to bind to a port below 1024).
     */
    private static final int TCP_FALLBACK_PORT = 4443;

    /**
     * The name of the property which specifies an additional port to be
     * advertised by the TCP harvester.
     */
    public static final String TCP_HARVESTER_MAPPED_PORT
        = "org.jitsi.videobridge.TCP_HARVESTER_MAPPED_PORT";

    /**
     * The name of the property which controls the port to which the
     * <tt>TcpHarvester</tt> will bind.
     */
    public static final String TCP_HARVESTER_PORT
        = "org.jitsi.videobridge.TCP_HARVESTER_PORT";

    /**
     * The name of the property which controls the use of ssltcp candidates by
     * <tt>TcpHarvester</tt>.
     */
    public static final String TCP_HARVESTER_SSLTCP
        = "org.jitsi.videobridge.TCP_HARVESTER_SSLTCP";

    /**
     * The name of the property that can be used to control the value of
     * {@link #ICE_UFRAG_PREFIX}.
     */
    public static final String ICE_UFRAG_PREFIX_PNAME
        = "org.jitsi.videobridge.ICE_UFRAG_PREFIX";

    /**
     * The optional prefix to use for generated ICE local username fragments.
     */
    private static String ICE_UFRAG_PREFIX;

    /**
     * The default value of the <tt>TCP_HARVESTER_SSLTCP</tt> property.
     */
    private static final boolean TCP_HARVESTER_SSLTCP_DEFAULT = true;

    /**
     * The single <tt>TcpHarvester</tt> instance for the
     * application.
     */
    private static TcpHarvester tcpHarvester = null;

    /**
     * The <tt>SinglePortUdpHarvester</tt>s which will be appended to ICE
     * <tt>Agent</tt>s managed by <tt>IceUdpTransportManager</tt> instances.
     */
    private static List<SinglePortUdpHarvester> singlePortHarvesters = null;

    /**
     * The flag which indicates whether application-wide harvesters, stored
     * in the static fields {@link #tcpHarvester} and
     * {@link #singlePortHarvesters} have been initialized.
     */
    private static boolean staticConfigurationInitialized = false;

    /**
     * The "mapped port" added to {@link #tcpHarvester}, or -1.
     */
    private static int tcpHarvesterMappedPort = -1;

    /**
     * Initializes the static <tt>Harvester</tt> instances used by all
     * <tt>IceUdpTransportManager</tt> instances, that is
     * {@link #tcpHarvester} and {@link #singlePortHarvesters}.
     *
     * @param cfg the {@link ConfigurationService} which provides values to
     * configurable properties of the behavior/logic of the method
     * implementation
     */
    private static void initializeStaticConfiguration(ConfigurationService cfg)
    {
        synchronized (IceUdpTransportManager.class)
        {
            if (staticConfigurationInitialized)
            {
                return;
            }
            staticConfigurationInitialized = true;

            ICE_UFRAG_PREFIX = cfg.getString(ICE_UFRAG_PREFIX_PNAME, null);

            int singlePort = cfg.getInt(SINGLE_PORT_HARVESTER_PORT,
                                        SINGLE_PORT_DEFAULT_VALUE);
            if (singlePort != -1)
            {
                singlePortHarvesters
                    = SinglePortUdpHarvester.createHarvesters(singlePort);
                if (singlePortHarvesters.isEmpty())
                {
                    singlePortHarvesters = null;
                    classLogger.info("No single-port harvesters created.");
                }
            }

            if (!cfg.getBoolean(DISABLE_TCP_HARVESTER, false))
            {
                int port = cfg.getInt(TCP_HARVESTER_PORT, -1);
                boolean fallback = false;
                boolean ssltcp = cfg.getBoolean(TCP_HARVESTER_SSLTCP,
                                                TCP_HARVESTER_SSLTCP_DEFAULT);

                if (port == -1)
                {
                    port = TCP_DEFAULT_PORT;
                    fallback = true;
                }

                try
                {
                    tcpHarvester = new TcpHarvester(port, ssltcp);
                }
                catch (IOException ioe)
                {
                    classLogger.warn(
                        "Failed to initialize TCP harvester on port " + port
                            + ": " + ioe
                            + (fallback
                            ? ". Retrying on port " + TCP_FALLBACK_PORT
                            : "")
                            + ".");
                    // If no fallback is allowed, the method will return.
                }
                if (tcpHarvester == null)
                {
                    // If TCP_HARVESTER_PORT specified a port, then fallback was
                    // disabled. However, if the binding on the port (above)
                    // fails, then the method should return.
                    if (!fallback)
                        return;

                    port = TCP_FALLBACK_PORT;
                    try
                    {
                        tcpHarvester
                            = new TcpHarvester(port, ssltcp);
                    }
                    catch (IOException ioe)
                    {
                        classLogger.warn(
                            "Failed to initialize TCP harvester on fallback"
                                + " port " + port + ": " + ioe);
                        return;
                    }
                }

                if (classLogger.isInfoEnabled())
                {
                    classLogger.info("Initialized TCP harvester on port " + port
                                         + ", using SSLTCP:" + ssltcp);
                }

                int mappedPort = cfg.getInt(TCP_HARVESTER_MAPPED_PORT, -1);
                if (mappedPort != -1)
                {
                    tcpHarvesterMappedPort = mappedPort;
                    tcpHarvester.addMappedPort(mappedPort);
                }
            }
        }
    }

    /**
     * The single (if any) <tt>Channel</tt> instance, whose sockets are
     * currently configured to accept DTLS packets.
     */
    private Channel channelForDtls = null;

    /**
     * Whether this <tt>TransportManager</tt> has been closed.
     */
    private boolean closed = false;

    /**
     * The <tt>Conference</tt> object that this <tt>TransportManager</tt> is
     * associated with.
     */
    private final Conference conference;

    /**
     * The <tt>Thread</tt> used by this <tt>TransportManager</tt> to wait until
     * {@link #iceAgent} has established a connection.
     */
    private Thread connectThread;

    /**
     * Used to synchronize access to {@link #connectThread}.
     */
    private final Object connectThreadSyncRoot = new Object();

    /**
     * The <tt>DtlsControl</tt> that this <tt>TransportManager</tt> uses.
     */
    private final DtlsControlImpl dtlsControl;

    /**
     * The <tt>Agent</tt> which implements the ICE protocol and which is used
     * by this instance to implement the Jingle ICE-UDP transport.
     */
    private Agent iceAgent;

    /**
     * The <tt>PropertyChangeListener</tt> which is (to be) notified about
     * changes in the <tt>state</tt> of {@link #iceAgent}.
     */
    private final PropertyChangeListener iceAgentStateChangeListener
        = new PropertyChangeListener()
        {
            @Override
            public void propertyChange(PropertyChangeEvent ev)
            {
                iceAgentStateChange(ev);
            }
        };

    /**
     * Whether ICE connectivity has been established.
     */
    private boolean iceConnected = false;

    /**
     * The <tt>IceMediaStream</tt> of {@link #iceAgent} associated with the
     * <tt>Channel</tt> of this instance.
     */
    private final IceMediaStream iceStream;

    /**
     * The <tt>PropertyChangeListener</tt> which is (to be) notified about
     * changes in the properties of the <tt>CandidatePair</tt>s of
     * {@link #iceStream}.
     */
    private final PropertyChangeListener iceStreamPairChangeListener
        = new PropertyChangeListener()
        {
            @Override
            public void propertyChange(PropertyChangeEvent ev)
            {
                iceStreamPairChange(ev);
            }
        };

    /**
     * Whether this <tt>IceUdpTransportManager</tt> will serve as the the
     * controlling or controlled ICE agent.
     */
    private final boolean controlling;

    /**
     * The number of {@link org.ice4j.ice.Component}-s to create in
     * {@link #iceStream}.
     */
    private int numComponents;

    /**
     * Whether we're using rtcp-mux or not.
     */
    private boolean rtcpmux;

    /**
     * The <tt>SctpConnection</tt> instance, if any, added as a <tt>Channel</tt>
     * to this <tt>IceUdpTransportManager</tt>.
     *
     * Currently we support a single <tt>SctpConnection</tt> in one
     * <tt>IceUdpTransportManager</tt> and if it exists, it will receive all
     * DTLS packets.
     */
    private SctpConnection sctpConnection = null;

    /**
     * The {@link Logger} to be used by this instance to print debug
     * information.
     */
    private final Logger logger;

    /**
     * Initializes a new <tt>IceUdpTransportManager</tt> instance.
     *
     * @param conference the <tt>Conference</tt> which created this
     * <tt>TransportManager</tt>.
     * @param controlling {@code true} if the new instance is to serve as a
     * controlling ICE agent and passive DTLS endpoint; otherwise, {@code false}
     * @throws IOException
     */
    public IceUdpTransportManager(Conference conference,
                                  boolean controlling)
        throws IOException
    {
        this(conference, controlling, 2, DEFAULT_ICE_STREAM_NAME);
    }

    public IceUdpTransportManager(Conference conference,
                                  boolean controlling,
                                  int numComponents)
        throws IOException
    {
        this(conference, controlling, numComponents, DEFAULT_ICE_STREAM_NAME);
    }

    /**
     * Initializes a new <tt>IceUdpTransportManager</tt> instance.
     *
     * @param conference the <tt>Conference</tt> which created this
     * <tt>TransportManager</tt>.
     * @param controlling {@code true} if the new instance is to serve as a
     * controlling ICE agent and passive DTLS endpoint; otherwise, {@code false}
     * @param numComponents the number of ICE components that this instance is
     * to start with.
     * @param iceStreamName the name of the ICE stream to be created by this
     * instance.
     * @throws IOException
     */
    public IceUdpTransportManager(Conference conference,
                                  boolean controlling,
                                  int numComponents,
                                  String iceStreamName)
        throws IOException
    {
        this.conference = conference;
        this.controlling = controlling;
        this.numComponents = numComponents;
        this.rtcpmux = numComponents == 1;
        this.logger = Logger.getLogger(classLogger, conference.getLogger());

        dtlsControl = createDtlsControl();

        iceAgent = createIceAgent(controlling, iceStreamName, rtcpmux);
        iceAgent.addStateChangeListener(iceAgentStateChangeListener);
        iceStream = iceAgent.getStream(iceStreamName);
        iceStream.addPairChangeListener(iceStreamPairChangeListener);

        EventAdmin eventAdmin = conference.getEventAdmin();
        if (eventAdmin != null)
        {
            eventAdmin.sendEvent(EventFactory.transportCreated(this));
        }
    }

    /**
     * Initializes a new <tt>IceUdpTransportManager</tt> instance.
     *
     * @param conference the <tt>Conference</tt> which created this
     * <tt>TransportManager</tt>.
     * @param controlling {@code true} if the new instance is to serve as a
     * controlling ICE agent and passive DTLS endpoint; otherwise, {@code false}
     * @param iceStreamName the name of the ICE stream to be created by this
     * instance.
     * @throws IOException
     */
    public IceUdpTransportManager(Conference conference,
                                  boolean controlling,
                                  String iceStreamName)
        throws IOException
    {
        this(conference, controlling, 2, iceStreamName);
    }

    /**
     * {@inheritDoc}
     *
     * Assures that no more than one <tt>SctpConnection</tt> is added. Keeps
     * {@link #sctpConnection} and {@link #channelForDtls} up to date.
     */
    @Override
    public boolean addChannel(Channel channel)
    {
        if (closed)
            return false;

        if (channel instanceof SctpConnection
                && sctpConnection != null
                && sctpConnection != channel)
        {
            logger.info(
                "Not adding a second SctpConnection to TransportManager.");
            return false;
        }

        if (!super.addChannel(channel))
            return false;

        if (channel instanceof SctpConnection)
        {
            // When an SctpConnection is added, it automatically replaces
            // channelForDtls, because it needs DTLS packets for the application
            // data inside them.
            sctpConnection = (SctpConnection) channel;
            if (channelForDtls != null)
            {
                // channelForDtls is necessarily an RtpChannel, because we don't
                // add more than one SctpConnection. The SctpConnection socket
                // will automatically accept DTLS.
                RtpChannel rtpChannelForDtls = (RtpChannel) channelForDtls;

                rtpChannelForDtls.getDatagramFilter(false).setAcceptNonRtp(
                        false);
                rtpChannelForDtls.getDatagramFilter(true).setAcceptNonRtp(
                        false);
            }
            channelForDtls = sctpConnection;
        }
        else if (channelForDtls == null)
        {
            channelForDtls = channel;

            RtpChannel rtpChannel = (RtpChannel) channel;

            // The new channelForDtls will always accept DTLS packets on its
            // RTP socket.
            rtpChannel.getDatagramFilter(false).setAcceptNonRtp(true);
            // If we use rtcpmux, we don't want to accept DTLS packets on the
            // RTCP socket, because they will be duplicated from the RTP socket,
            // because both sockets are actually filters on the same underlying
            // socket.
            rtpChannel.getDatagramFilter(true).setAcceptNonRtp(!rtcpmux);
        }

        if (iceConnected)
            channel.transportConnected();

        EventAdmin eventAdmin = conference.getEventAdmin();
        if (eventAdmin != null)
        {
            eventAdmin.sendEvent(EventFactory.transportChannelAdded(channel));
        }

        return true;
    }

    private int addRemoteCandidates(
            List<CandidatePacketExtension> candidates,
            boolean iceAgentStateIsRunning)
    {
        // Sort the remote candidates (host < reflexive < relayed) in order to
        // create first the host, then the reflexive, the relayed candidates and
        // thus be able to set the relative-candidate matching the
        // rel-addr/rel-port attribute.
        Collections.sort(candidates);

        int generation = iceAgent.getGeneration();
        int remoteCandidateCount = 0;

        for (CandidatePacketExtension candidate : candidates)
        {
            // Is the remote candidate from the current generation of the
            // iceAgent?
            if (candidate.getGeneration() != generation)
                continue;

            if (rtcpmux && Component.RTCP == candidate.getComponent())
            {
                logger.warn(
                        "Received an RTCP candidate, but we're using rtcp-mux."
                            + " Ignoring.");
                continue;
            }

            Component component
                = iceStream.getComponent(candidate.getComponent());
            String relAddr;
            int relPort;
            TransportAddress relatedAddress = null;

            if ((relAddr = candidate.getRelAddr()) != null
                    && (relPort = candidate.getRelPort()) != -1)
            {
                relatedAddress
                    = new TransportAddress(
                            relAddr,
                            relPort,
                            Transport.parse(candidate.getProtocol()));
            }

            RemoteCandidate relatedCandidate
                = component.findRemoteCandidate(relatedAddress);
            RemoteCandidate remoteCandidate
                = new RemoteCandidate(
                        new TransportAddress(
                                candidate.getIP(),
                                candidate.getPort(),
                                Transport.parse(candidate.getProtocol())),
                        component,
                        org.ice4j.ice.CandidateType.parse(
                                candidate.getType().toString()),
                        candidate.getFoundation(),
                        candidate.getPriority(),
                        relatedCandidate);

            // XXX IceUdpTransportManager harvests host candidates only and the
            // ICE Components utilize the UDP protocol/transport only at the
            // time of this writing. The ice4j library will, of course, check
            // the theoretical reachability between the local and the remote
            // candidates. However, we would like (1) to not mess with a
            // possibly running iceAgent and (2) to return a consistent return
            // value.
            if (!canReach(component, remoteCandidate))
                continue;

            if (iceAgentStateIsRunning)
                component.addUpdateRemoteCandidates(remoteCandidate);
            else
                component.addRemoteCandidate(remoteCandidate);
            remoteCandidateCount++;
        }

        return remoteCandidateCount;
    }

    /**
     * Adds to <tt>iceAgent</tt> videobridge specific candidate harvesters such
     * as an Amazon AWS EC2 specific harvester.
     *
     * @param iceAgent the {@link Agent} that we'd like to append new harvesters
     * to.
     * @param rtcpmux whether rtcp will be used by this
     * <tt>IceUdpTransportManager</tt>.
     */
    private void appendVideobridgeHarvesters(Agent iceAgent,
                                             boolean rtcpmux)
    {
        ConfigurationService cfg
            = ServiceUtils.getService(
                    getBundleContext(),
                    ConfigurationService.class);
        boolean enableDynamicHostHarvester = true;

        if (rtcpmux)
        {
            // TODO CandidateHarvesters may take (non-trivial) time to
            // initialize so initialize them as soon as possible, don't wait to
            // initialize them after a Channel is requested.
            // XXX Unfortunately, TcpHarvester binds to specific local addresses
            // while Jetty binds to all/any local addresses and, consequently,
            // the order of the binding is important at the time of this
            // writing. That's why TcpHarvester is left to initialize as late as
            // possible right now.
            initializeStaticConfiguration(cfg);

            if (tcpHarvester != null)
                iceAgent.addCandidateHarvester(tcpHarvester);
            if (singlePortHarvesters != null)
            {
                for (CandidateHarvester harvester : singlePortHarvesters)
                {
                    iceAgent.addCandidateHarvester(harvester);
                    enableDynamicHostHarvester = false;
                }
            }
        }

        // Use dynamic ports iff we're not using "single port".
        iceAgent.setUseHostHarvester(enableDynamicHostHarvester);
    }

    /**
     * Determines whether at least one <tt>LocalCandidate</tt> of a specific ICE
     * <tt>Component</tt> can reach (in the terms of the ice4j library) a
     * specific <tt>RemoteCandidate</tt>
     *
     * @param component the ICE <tt>Component</tt> which contains the
     * <tt>LocalCandidate</tt>s to check whether at least one of them can reach
     * the specified <tt>remoteCandidate</tt>
     * @param remoteCandidate the <tt>RemoteCandidate</tt> to check whether at
     * least one of the <tt>LocalCandidate</tt>s of the specified
     * <tt>component</tt> can reach it
     * @return <tt>true</tt> if at least one <tt>LocalCandidate</tt> of the
     * specified <tt>component</tt> can reach the specified
     * <tt>remoteCandidate</tt>
     */
    private boolean canReach(
            Component component,
            RemoteCandidate remoteCandidate)
    {
        for (LocalCandidate localCandidate : component.getLocalCandidates())
        {
            if (localCandidate.canReach(remoteCandidate))
                return true;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     *
     * TODO: In the case of multiple {@code Channel}s in one
     * {@code TransportManager} it is unclear how to handle changes to the
     * {@code initiator} property from individual channels.
     */
    @Override
    protected void channelPropertyChange(PropertyChangeEvent ev)
    {
        super.channelPropertyChange(ev);

        /*
        if (Channel.INITIATOR_PROPERTY.equals(ev.getPropertyName())
                && iceAgent != null)
        {
            Channel channel = (Channel) ev.getSource();

            iceAgent.setControlling(channel.isInitiator());
        }
        */
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void close()
    {
        if (!closed)
        {
            // Set this early to prevent double closing when the last channel
            // is removed.
            closed = true;

            for (Channel channel : getChannels())
                close(channel);

            if (dtlsControl != null)
            {
                dtlsControl.start(null); //stop
                dtlsControl.cleanup(this);
            }

//            DatagramSocket[] datagramSockets = getStreamConnectorSockets();

            if (iceStream != null)
            {
                iceStream.removePairStateChangeListener(
                        iceStreamPairChangeListener);
            }
            if (iceAgent != null)
            {
                iceAgent.removeStateChangeListener(iceAgentStateChangeListener);
                iceAgent.free();
                iceAgent = null;
            }

            /*
             * It seems that the ICE agent takes care of closing these.
             *
            if (datagramSockets != null)
            {
                if (datagramSockets[0] != null)
                    datagramSockets[0].close();
                if (datagramSockets[1] != null)
                    datagramSockets[1].close();
            }
            */

            synchronized (connectThreadSyncRoot)
            {
                if (connectThread != null)
                    connectThread.interrupt();
            }

            super.close();
        }
    }

    /**
     * {@inheritDoc}
     *
     * Keeps {@link #sctpConnection} and {@link #channelForDtls} up to date.
     */
    @Override
    public boolean close(Channel channel)
    {
        boolean removed = super.close(channel);

        if (removed)
        {
            if (channel == sctpConnection)
            {
                sctpConnection = null;
            }

            if (channel == channelForDtls)
            {
                if (sctpConnection != null)
                {
                    channelForDtls = sctpConnection;
                }
                else if (channel instanceof RtpChannel)
                {
                    RtpChannel newChannelForDtls = null;

                    for (Channel c : getChannels())
                    {
                        if (c instanceof RtpChannel)
                            newChannelForDtls = (RtpChannel) c;
                    }
                    if (newChannelForDtls != null)
                    {
                        newChannelForDtls.getDatagramFilter(false)
                                .setAcceptNonRtp(true);
                        newChannelForDtls.getDatagramFilter(true)
                                .setAcceptNonRtp(!rtcpmux);
                    }
                    channelForDtls = newChannelForDtls;
                }

                if (channel instanceof RtpChannel)
                {
                    RtpChannel rtpChannel = (RtpChannel) channel;

                    rtpChannel.getDatagramFilter(false).setAcceptNonRtp(false);
                    rtpChannel.getDatagramFilter(true).setAcceptNonRtp(false);
                }
            }

            try
            {
                StreamConnector connector = channel.getStreamConnector();

                if (connector != null)
                {
                    DatagramSocket datagramSocket = connector.getDataSocket();

                    if (datagramSocket != null)
                        datagramSocket.close();
                    datagramSocket = connector.getControlSocket();
                    if (datagramSocket != null)
                        datagramSocket.close();

                    Socket socket = connector.getDataTCPSocket();

                    if (socket != null)
                        socket.close();
                    socket = connector.getControlTCPSocket();
                    if (socket != null)
                        socket.close();
                }
            }
            catch (IOException ioe)
            {
                logger.info(
                    "Failed to close sockets when closing a channel:" + ioe);
            }

            EventAdmin eventAdmin = conference.getEventAdmin();
            if (eventAdmin != null)
            {
                eventAdmin.sendEvent(
                        EventFactory.transportChannelRemoved(channel));
            }

            channel.transportClosed();
        }

        if (getChannels().isEmpty())
            close();

        return removed;
    }

    /**
     * Initializes a new {@code DtlsControlImpl} instance.
     *
     * @return a new {@code DtlsControlImpl} instance
     */
    private DtlsControlImpl createDtlsControl()
    {
        DtlsControlImpl dtlsControl
            = new DtlsControlImpl(/* srtpDisabled */ false);

        dtlsControl.registerUser(this);
        // (1) According to https://tools.ietf.org/html/rfc5245#section-5.2, in
        // the case of two full ICE agents: [t]he agent that generated the offer
        // which started the ICE processing MUST take the controlling role, and
        // the other MUST take the controlled role.
        // (2) According to https://tools.ietf.org/html/rfc5763#section-5: [t]he
        // endpoint that is the offerer MUST use the setup attribute value of
        // setup:actpass and be prepared to receive a client_hello before it
        // receives the answer. The answerer MUST use either a setup attribute
        // value of setup:active or setup:passive.
        dtlsControl.setSetup(
                controlling
                    ? DtlsControl.Setup.ACTPASS
                    : DtlsControl.Setup.ACTIVE);
        // XXX For DTLS, the media type doesn't matter (as long as it's not
        // null).
        // XXX The actual start of the DTLS servers/clients will be delayed
        // until an rtpConnector is set (when a MediaStream with this
        // SrtpControl starts or is assigned a target).
        dtlsControl.start(MediaType.AUDIO);
        return dtlsControl;
    }

    /**
     * Initializes a new <tt>Agent</tt> instance which implements the ICE
     * protocol and which is to be used by this instance to implement the Jingle
     * ICE-UDP transport.
     *
     * @param controlling
     * @param iceStreamName
     * @param rtcpmux
     * @return a new <tt>Agent</tt> instance which implements the ICE protocol
     * and which is to be used by this instance to implement the Jingle ICE-UDP
     * transport
     * @throws IOException if initializing a new <tt>Agent</tt> instance for the
     * purposes of this <tt>TransportManager</tt> fails
     */
    private Agent createIceAgent(boolean controlling,
                                 String iceStreamName,
                                 boolean rtcpmux)
            throws IOException
    {
        NetworkAddressManagerService nams
            = ServiceUtils.getService(
                    getBundleContext(),
                    NetworkAddressManagerService.class);
        Agent iceAgent = new Agent(logger.getLevel(), ICE_UFRAG_PREFIX);

        //add videobridge specific harvesters such as a mapping and an Amazon
        //AWS EC2 harvester
        appendVideobridgeHarvesters(iceAgent, rtcpmux);<commit_msg>style: Uses camel case for a non-final variable.<commit_after>/*
 * 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.jitsi.videobridge;

import java.beans.*;
import java.io.*;
import java.net.*;
import java.util.*;

import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.CandidateType;
import net.java.sip.communicator.service.netaddr.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.*;

import org.ice4j.*;
import org.ice4j.ice.*;
import org.ice4j.ice.harvest.*;
import org.ice4j.socket.*;
import org.jitsi.eventadmin.*;
import org.jitsi.impl.neomedia.transform.dtls.*;
import org.jitsi.service.configuration.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.util.*;
import org.jitsi.util.Logger;
import org.osgi.framework.*;

/**
 * Implements the Jingle ICE-UDP transport.
 *
 * @author <NAME>
 * @author <NAME>
 * @author <NAME>
 */
public class IceUdpTransportManager
    extends TransportManager
{
    /**
     * The name default of the single <tt>IceStream</tt> that this
     * <tt>TransportManager</tt> will create/use.
     */
    private static final String DEFAULT_ICE_STREAM_NAME = "stream";

    /**
     * The name of the property which disables the use of a
     * <tt>TcpHarvester</tt>.
     */
    public static final String DISABLE_TCP_HARVESTER
        = "org.jitsi.videobridge.DISABLE_TCP_HARVESTER";

    /**
     * The name of the property which controls the port number used for
     * <tt>SinglePortUdpHarvester</tt>s.
     */
    public static final String SINGLE_PORT_HARVESTER_PORT
            = "org.jitsi.videobridge.SINGLE_PORT_HARVESTER_PORT";

    /**
     * The default value of the port to be used for
     * {@code SinglePortUdpHarvester}.
     */
    private static final int SINGLE_PORT_DEFAULT_VALUE = 10000;

    /**
     * The {@link Logger} used by the {@link IceUdpTransportManager} class to
     * print debug information. Note that instances should use {@link #logger}
     * instead.
     */
    private static final Logger classLogger
        = Logger.getLogger(IceUdpTransportManager.class);

    /**
     * The default port that the <tt>TcpHarvester</tt> will
     * bind to.
     */
    private static final int TCP_DEFAULT_PORT = 443;

    /**
     * The port on which the <tt>TcpHarvester</tt> will bind to
     * if no port is specifically configured, and binding to
     * <tt>DEFAULT_TCP_PORT</tt> fails (for example, if the process doesn't have
     * the required privileges to bind to a port below 1024).
     */
    private static final int TCP_FALLBACK_PORT = 4443;

    /**
     * The name of the property which specifies an additional port to be
     * advertised by the TCP harvester.
     */
    public static final String TCP_HARVESTER_MAPPED_PORT
        = "org.jitsi.videobridge.TCP_HARVESTER_MAPPED_PORT";

    /**
     * The name of the property which controls the port to which the
     * <tt>TcpHarvester</tt> will bind.
     */
    public static final String TCP_HARVESTER_PORT
        = "org.jitsi.videobridge.TCP_HARVESTER_PORT";

    /**
     * The name of the property which controls the use of ssltcp candidates by
     * <tt>TcpHarvester</tt>.
     */
    public static final String TCP_HARVESTER_SSLTCP
        = "org.jitsi.videobridge.TCP_HARVESTER_SSLTCP";

    /**
     * The name of the property that can be used to control the value of
     * {@link #iceUfragPrefix}.
     */
    public static final String ICE_UFRAG_PREFIX_PNAME
        = "org.jitsi.videobridge.ICE_UFRAG_PREFIX";

    /**
     * The optional prefix to use for generated ICE local username fragments.
     */
    private static String iceUfragPrefix;

    /**
     * The default value of the <tt>TCP_HARVESTER_SSLTCP</tt> property.
     */
    private static final boolean TCP_HARVESTER_SSLTCP_DEFAULT = true;

    /**
     * The single <tt>TcpHarvester</tt> instance for the
     * application.
     */
    private static TcpHarvester tcpHarvester = null;

    /**
     * The <tt>SinglePortUdpHarvester</tt>s which will be appended to ICE
     * <tt>Agent</tt>s managed by <tt>IceUdpTransportManager</tt> instances.
     */
    private static List<SinglePortUdpHarvester> singlePortHarvesters = null;

    /**
     * The flag which indicates whether application-wide harvesters, stored
     * in the static fields {@link #tcpHarvester} and
     * {@link #singlePortHarvesters} have been initialized.
     */
    private static boolean staticConfigurationInitialized = false;

    /**
     * The "mapped port" added to {@link #tcpHarvester}, or -1.
     */
    private static int tcpHarvesterMappedPort = -1;

    /**
     * Initializes the static <tt>Harvester</tt> instances used by all
     * <tt>IceUdpTransportManager</tt> instances, that is
     * {@link #tcpHarvester} and {@link #singlePortHarvesters}.
     *
     * @param cfg the {@link ConfigurationService} which provides values to
     * configurable properties of the behavior/logic of the method
     * implementation
     */
    private static void initializeStaticConfiguration(ConfigurationService cfg)
    {
        synchronized (IceUdpTransportManager.class)
        {
            if (staticConfigurationInitialized)
            {
                return;
            }
            staticConfigurationInitialized = true;

            iceUfragPrefix = cfg.getString(ICE_UFRAG_PREFIX_PNAME, null);

            int singlePort = cfg.getInt(SINGLE_PORT_HARVESTER_PORT,
                                        SINGLE_PORT_DEFAULT_VALUE);
            if (singlePort != -1)
            {
                singlePortHarvesters
                    = SinglePortUdpHarvester.createHarvesters(singlePort);
                if (singlePortHarvesters.isEmpty())
                {
                    singlePortHarvesters = null;
                    classLogger.info("No single-port harvesters created.");
                }
            }

            if (!cfg.getBoolean(DISABLE_TCP_HARVESTER, false))
            {
                int port = cfg.getInt(TCP_HARVESTER_PORT, -1);
                boolean fallback = false;
                boolean ssltcp = cfg.getBoolean(TCP_HARVESTER_SSLTCP,
                                                TCP_HARVESTER_SSLTCP_DEFAULT);

                if (port == -1)
                {
                    port = TCP_DEFAULT_PORT;
                    fallback = true;
                }

                try
                {
                    tcpHarvester = new TcpHarvester(port, ssltcp);
                }
                catch (IOException ioe)
                {
                    classLogger.warn(
                        "Failed to initialize TCP harvester on port " + port
                            + ": " + ioe
                            + (fallback
                            ? ". Retrying on port " + TCP_FALLBACK_PORT
                            : "")
                            + ".");
                    // If no fallback is allowed, the method will return.
                }
                if (tcpHarvester == null)
                {
                    // If TCP_HARVESTER_PORT specified a port, then fallback was
                    // disabled. However, if the binding on the port (above)
                    // fails, then the method should return.
                    if (!fallback)
                        return;

                    port = TCP_FALLBACK_PORT;
                    try
                    {
                        tcpHarvester
                            = new TcpHarvester(port, ssltcp);
                    }
                    catch (IOException ioe)
                    {
                        classLogger.warn(
                            "Failed to initialize TCP harvester on fallback"
                                + " port " + port + ": " + ioe);
                        return;
                    }
                }

                if (classLogger.isInfoEnabled())
                {
                    classLogger.info("Initialized TCP harvester on port " + port
                                         + ", using SSLTCP:" + ssltcp);
                }

                int mappedPort = cfg.getInt(TCP_HARVESTER_MAPPED_PORT, -1);
                if (mappedPort != -1)
                {
                    tcpHarvesterMappedPort = mappedPort;
                    tcpHarvester.addMappedPort(mappedPort);
                }
            }
        }
    }

    /**
     * The single (if any) <tt>Channel</tt> instance, whose sockets are
     * currently configured to accept DTLS packets.
     */
    private Channel channelForDtls = null;

    /**
     * Whether this <tt>TransportManager</tt> has been closed.
     */
    private boolean closed = false;

    /**
     * The <tt>Conference</tt> object that this <tt>TransportManager</tt> is
     * associated with.
     */
    private final Conference conference;

    /**
     * The <tt>Thread</tt> used by this <tt>TransportManager</tt> to wait until
     * {@link #iceAgent} has established a connection.
     */
    private Thread connectThread;

    /**
     * Used to synchronize access to {@link #connectThread}.
     */
    private final Object connectThreadSyncRoot = new Object();

    /**
     * The <tt>DtlsControl</tt> that this <tt>TransportManager</tt> uses.
     */
    private final DtlsControlImpl dtlsControl;

    /**
     * The <tt>Agent</tt> which implements the ICE protocol and which is used
     * by this instance to implement the Jingle ICE-UDP transport.
     */
    private Agent iceAgent;

    /**
     * The <tt>PropertyChangeListener</tt> which is (to be) notified about
     * changes in the <tt>state</tt> of {@link #iceAgent}.
     */
    private final PropertyChangeListener iceAgentStateChangeListener
        = new PropertyChangeListener()
        {
            @Override
            public void propertyChange(PropertyChangeEvent ev)
            {
                iceAgentStateChange(ev);
            }
        };

    /**
     * Whether ICE connectivity has been established.
     */
    private boolean iceConnected = false;

    /**
     * The <tt>IceMediaStream</tt> of {@link #iceAgent} associated with the
     * <tt>Channel</tt> of this instance.
     */
    private final IceMediaStream iceStream;

    /**
     * The <tt>PropertyChangeListener</tt> which is (to be) notified about
     * changes in the properties of the <tt>CandidatePair</tt>s of
     * {@link #iceStream}.
     */
    private final PropertyChangeListener iceStreamPairChangeListener
        = new PropertyChangeListener()
        {
            @Override
            public void propertyChange(PropertyChangeEvent ev)
            {
                iceStreamPairChange(ev);
            }
        };

    /**
     * Whether this <tt>IceUdpTransportManager</tt> will serve as the the
     * controlling or controlled ICE agent.
     */
    private final boolean controlling;

    /**
     * The number of {@link org.ice4j.ice.Component}-s to create in
     * {@link #iceStream}.
     */
    private int numComponents;

    /**
     * Whether we're using rtcp-mux or not.
     */
    private boolean rtcpmux;

    /**
     * The <tt>SctpConnection</tt> instance, if any, added as a <tt>Channel</tt>
     * to this <tt>IceUdpTransportManager</tt>.
     *
     * Currently we support a single <tt>SctpConnection</tt> in one
     * <tt>IceUdpTransportManager</tt> and if it exists, it will receive all
     * DTLS packets.
     */
    private SctpConnection sctpConnection = null;

    /**
     * The {@link Logger} to be used by this instance to print debug
     * information.
     */
    private final Logger logger;

    /**
     * Initializes a new <tt>IceUdpTransportManager</tt> instance.
     *
     * @param conference the <tt>Conference</tt> which created this
     * <tt>TransportManager</tt>.
     * @param controlling {@code true} if the new instance is to serve as a
     * controlling ICE agent and passive DTLS endpoint; otherwise, {@code false}
     * @throws IOException
     */
    public IceUdpTransportManager(Conference conference,
                                  boolean controlling)
        throws IOException
    {
        this(conference, controlling, 2, DEFAULT_ICE_STREAM_NAME);
    }

    public IceUdpTransportManager(Conference conference,
                                  boolean controlling,
                                  int numComponents)
        throws IOException
    {
        this(conference, controlling, numComponents, DEFAULT_ICE_STREAM_NAME);
    }

    /**
     * Initializes a new <tt>IceUdpTransportManager</tt> instance.
     *
     * @param conference the <tt>Conference</tt> which created this
     * <tt>TransportManager</tt>.
     * @param controlling {@code true} if the new instance is to serve as a
     * controlling ICE agent and passive DTLS endpoint; otherwise, {@code false}
     * @param numComponents the number of ICE components that this instance is
     * to start with.
     * @param iceStreamName the name of the ICE stream to be created by this
     * instance.
     * @throws IOException
     */
    public IceUdpTransportManager(Conference conference,
                                  boolean controlling,
                                  int numComponents,
                                  String iceStreamName)
        throws IOException
    {
        this.conference = conference;
        this.controlling = controlling;
        this.numComponents = numComponents;
        this.rtcpmux = numComponents == 1;
        this.logger = Logger.getLogger(classLogger, conference.getLogger());

        dtlsControl = createDtlsControl();

        iceAgent = createIceAgent(controlling, iceStreamName, rtcpmux);
        iceAgent.addStateChangeListener(iceAgentStateChangeListener);
        iceStream = iceAgent.getStream(iceStreamName);
        iceStream.addPairChangeListener(iceStreamPairChangeListener);

        EventAdmin eventAdmin = conference.getEventAdmin();
        if (eventAdmin != null)
        {
            eventAdmin.sendEvent(EventFactory.transportCreated(this));
        }
    }

    /**
     * Initializes a new <tt>IceUdpTransportManager</tt> instance.
     *
     * @param conference the <tt>Conference</tt> which created this
     * <tt>TransportManager</tt>.
     * @param controlling {@code true} if the new instance is to serve as a
     * controlling ICE agent and passive DTLS endpoint; otherwise, {@code false}
     * @param iceStreamName the name of the ICE stream to be created by this
     * instance.
     * @throws IOException
     */
    public IceUdpTransportManager(Conference conference,
                                  boolean controlling,
                                  String iceStreamName)
        throws IOException
    {
        this(conference, controlling, 2, iceStreamName);
    }

    /**
     * {@inheritDoc}
     *
     * Assures that no more than one <tt>SctpConnection</tt> is added. Keeps
     * {@link #sctpConnection} and {@link #channelForDtls} up to date.
     */
    @Override
    public boolean addChannel(Channel channel)
    {
        if (closed)
            return false;

        if (channel instanceof SctpConnection
                && sctpConnection != null
                && sctpConnection != channel)
        {
            logger.info(
                "Not adding a second SctpConnection to TransportManager.");
            return false;
        }

        if (!super.addChannel(channel))
            return false;

        if (channel instanceof SctpConnection)
        {
            // When an SctpConnection is added, it automatically replaces
            // channelForDtls, because it needs DTLS packets for the application
            // data inside them.
            sctpConnection = (SctpConnection) channel;
            if (channelForDtls != null)
            {
                // channelForDtls is necessarily an RtpChannel, because we don't
                // add more than one SctpConnection. The SctpConnection socket
                // will automatically accept DTLS.
                RtpChannel rtpChannelForDtls = (RtpChannel) channelForDtls;

                rtpChannelForDtls.getDatagramFilter(false).setAcceptNonRtp(
                        false);
                rtpChannelForDtls.getDatagramFilter(true).setAcceptNonRtp(
                        false);
            }
            channelForDtls = sctpConnection;
        }
        else if (channelForDtls == null)
        {
            channelForDtls = channel;

            RtpChannel rtpChannel = (RtpChannel) channel;

            // The new channelForDtls will always accept DTLS packets on its
            // RTP socket.
            rtpChannel.getDatagramFilter(false).setAcceptNonRtp(true);
            // If we use rtcpmux, we don't want to accept DTLS packets on the
            // RTCP socket, because they will be duplicated from the RTP socket,
            // because both sockets are actually filters on the same underlying
            // socket.
            rtpChannel.getDatagramFilter(true).setAcceptNonRtp(!rtcpmux);
        }

        if (iceConnected)
            channel.transportConnected();

        EventAdmin eventAdmin = conference.getEventAdmin();
        if (eventAdmin != null)
        {
            eventAdmin.sendEvent(EventFactory.transportChannelAdded(channel));
        }

        return true;
    }

    private int addRemoteCandidates(
            List<CandidatePacketExtension> candidates,
            boolean iceAgentStateIsRunning)
    {
        // Sort the remote candidates (host < reflexive < relayed) in order to
        // create first the host, then the reflexive, the relayed candidates and
        // thus be able to set the relative-candidate matching the
        // rel-addr/rel-port attribute.
        Collections.sort(candidates);

        int generation = iceAgent.getGeneration();
        int remoteCandidateCount = 0;

        for (CandidatePacketExtension candidate : candidates)
        {
            // Is the remote candidate from the current generation of the
            // iceAgent?
            if (candidate.getGeneration() != generation)
                continue;

            if (rtcpmux && Component.RTCP == candidate.getComponent())
            {
                logger.warn(
                        "Received an RTCP candidate, but we're using rtcp-mux."
                            + " Ignoring.");
                continue;
            }

            Component component
                = iceStream.getComponent(candidate.getComponent());
            String relAddr;
            int relPort;
            TransportAddress relatedAddress = null;

            if ((relAddr = candidate.getRelAddr()) != null
                    && (relPort = candidate.getRelPort()) != -1)
            {
                relatedAddress
                    = new TransportAddress(
                            relAddr,
                            relPort,
                            Transport.parse(candidate.getProtocol()));
            }

            RemoteCandidate relatedCandidate
                = component.findRemoteCandidate(relatedAddress);
            RemoteCandidate remoteCandidate
                = new RemoteCandidate(
                        new TransportAddress(
                                candidate.getIP(),
                                candidate.getPort(),
                                Transport.parse(candidate.getProtocol())),
                        component,
                        org.ice4j.ice.CandidateType.parse(
                                candidate.getType().toString()),
                        candidate.getFoundation(),
                        candidate.getPriority(),
                        relatedCandidate);

            // XXX IceUdpTransportManager harvests host candidates only and the
            // ICE Components utilize the UDP protocol/transport only at the
            // time of this writing. The ice4j library will, of course, check
            // the theoretical reachability between the local and the remote
            // candidates. However, we would like (1) to not mess with a
            // possibly running iceAgent and (2) to return a consistent return
            // value.
            if (!canReach(component, remoteCandidate))
                continue;

            if (iceAgentStateIsRunning)
                component.addUpdateRemoteCandidates(remoteCandidate);
            else
                component.addRemoteCandidate(remoteCandidate);
            remoteCandidateCount++;
        }

        return remoteCandidateCount;
    }

    /**
     * Adds to <tt>iceAgent</tt> videobridge specific candidate harvesters such
     * as an Amazon AWS EC2 specific harvester.
     *
     * @param iceAgent the {@link Agent} that we'd like to append new harvesters
     * to.
     * @param rtcpmux whether rtcp will be used by this
     * <tt>IceUdpTransportManager</tt>.
     */
    private void appendVideobridgeHarvesters(Agent iceAgent,
                                             boolean rtcpmux)
    {
        ConfigurationService cfg
            = ServiceUtils.getService(
                    getBundleContext(),
                    ConfigurationService.class);
        boolean enableDynamicHostHarvester = true;

        if (rtcpmux)
        {
            // TODO CandidateHarvesters may take (non-trivial) time to
            // initialize so initialize them as soon as possible, don't wait to
            // initialize them after a Channel is requested.
            // XXX Unfortunately, TcpHarvester binds to specific local addresses
            // while Jetty binds to all/any local addresses and, consequently,
            // the order of the binding is important at the time of this
            // writing. That's why TcpHarvester is left to initialize as late as
            // possible right now.
            initializeStaticConfiguration(cfg);

            if (tcpHarvester != null)
                iceAgent.addCandidateHarvester(tcpHarvester);
            if (singlePortHarvesters != null)
            {
                for (CandidateHarvester harvester : singlePortHarvesters)
                {
                    iceAgent.addCandidateHarvester(harvester);
                    enableDynamicHostHarvester = false;
                }
            }
        }

        // Use dynamic ports iff we're not using "single port".
        iceAgent.setUseHostHarvester(enableDynamicHostHarvester);
    }

    /**
     * Determines whether at least one <tt>LocalCandidate</tt> of a specific ICE
     * <tt>Component</tt> can reach (in the terms of the ice4j library) a
     * specific <tt>RemoteCandidate</tt>
     *
     * @param component the ICE <tt>Component</tt> which contains the
     * <tt>LocalCandidate</tt>s to check whether at least one of them can reach
     * the specified <tt>remoteCandidate</tt>
     * @param remoteCandidate the <tt>RemoteCandidate</tt> to check whether at
     * least one of the <tt>LocalCandidate</tt>s of the specified
     * <tt>component</tt> can reach it
     * @return <tt>true</tt> if at least one <tt>LocalCandidate</tt> of the
     * specified <tt>component</tt> can reach the specified
     * <tt>remoteCandidate</tt>
     */
    private boolean canReach(
            Component component,
            RemoteCandidate remoteCandidate)
    {
        for (LocalCandidate localCandidate : component.getLocalCandidates())
        {
            if (localCandidate.canReach(remoteCandidate))
                return true;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     *
     * TODO: In the case of multiple {@code Channel}s in one
     * {@code TransportManager} it is unclear how to handle changes to the
     * {@code initiator} property from individual channels.
     */
    @Override
    protected void channelPropertyChange(PropertyChangeEvent ev)
    {
        super.channelPropertyChange(ev);

        /*
        if (Channel.INITIATOR_PROPERTY.equals(ev.getPropertyName())
                && iceAgent != null)
        {
            Channel channel = (Channel) ev.getSource();

            iceAgent.setControlling(channel.isInitiator());
        }
        */
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void close()
    {
        if (!closed)
        {
            // Set this early to prevent double closing when the last channel
            // is removed.
            closed = true;

            for (Channel channel : getChannels())
                close(channel);

            if (dtlsControl != null)
            {
                dtlsControl.start(null); //stop
                dtlsControl.cleanup(this);
            }

//            DatagramSocket[] datagramSockets = getStreamConnectorSockets();

            if (iceStream != null)
            {
                iceStream.removePairStateChangeListener(
                        iceStreamPairChangeListener);
            }
            if (iceAgent != null)
            {
                iceAgent.removeStateChangeListener(iceAgentStateChangeListener);
                iceAgent.free();
                iceAgent = null;
            }

            /*
             * It seems that the ICE agent takes care of closing these.
             *
            if (datagramSockets != null)
            {
                if (datagramSockets[0] != null)
                    datagramSockets[0].close();
                if (datagramSockets[1] != null)
                    datagramSockets[1].close();
            }
            */

            synchronized (connectThreadSyncRoot)
            {
                if (connectThread != null)
                    connectThread.interrupt();
            }

            super.close();
        }
    }

    /**
     * {@inheritDoc}
     *
     * Keeps {@link #sctpConnection} and {@link #channelForDtls} up to date.
     */
    @Override
    public boolean close(Channel channel)
    {
        boolean removed = super.close(channel);

        if (removed)
        {
            if (channel == sctpConnection)
            {
                sctpConnection = null;
            }

            if (channel == channelForDtls)
            {
                if (sctpConnection != null)
                {
                    channelForDtls = sctpConnection;
                }
                else if (channel instanceof RtpChannel)
                {
                    RtpChannel newChannelForDtls = null;

                    for (Channel c : getChannels())
                    {
                        if (c instanceof RtpChannel)
                            newChannelForDtls = (RtpChannel) c;
                    }
                    if (newChannelForDtls != null)
                    {
                        newChannelForDtls.getDatagramFilter(false)
                                .setAcceptNonRtp(true);
                        newChannelForDtls.getDatagramFilter(true)
                                .setAcceptNonRtp(!rtcpmux);
                    }
                    channelForDtls = newChannelForDtls;
                }

                if (channel instanceof RtpChannel)
                {
                    RtpChannel rtpChannel = (RtpChannel) channel;

                    rtpChannel.getDatagramFilter(false).setAcceptNonRtp(false);
                    rtpChannel.getDatagramFilter(true).setAcceptNonRtp(false);
                }
            }

            try
            {
                StreamConnector connector = channel.getStreamConnector();

                if (connector != null)
                {
                    DatagramSocket datagramSocket = connector.getDataSocket();

                    if (datagramSocket != null)
                        datagramSocket.close();
                    datagramSocket = connector.getControlSocket();
                    if (datagramSocket != null)
                        datagramSocket.close();

                    Socket socket = connector.getDataTCPSocket();

                    if (socket != null)
                        socket.close();
                    socket = connector.getControlTCPSocket();
                    if (socket != null)
                        socket.close();
                }
            }
            catch (IOException ioe)
            {
                logger.info(
                    "Failed to close sockets when closing a channel:" + ioe);
            }

            EventAdmin eventAdmin = conference.getEventAdmin();
            if (eventAdmin != null)
            {
                eventAdmin.sendEvent(
                        EventFactory.transportChannelRemoved(channel));
            }

            channel.transportClosed();
        }

        if (getChannels().isEmpty())
            close();

        return removed;
    }

    /**
     * Initializes a new {@code DtlsControlImpl} instance.
     *
     * @return a new {@code DtlsControlImpl} instance
     */
    private DtlsControlImpl createDtlsControl()
    {
        DtlsControlImpl dtlsControl
            = new DtlsControlImpl(/* srtpDisabled */ false);

        dtlsControl.registerUser(this);
        // (1) According to https://tools.ietf.org/html/rfc5245#section-5.2, in
        // the case of two full ICE agents: [t]he agent that generated the offer
        // which started the ICE processing MUST take the controlling role, and
        // the other MUST take the controlled role.
        // (2) According to https://tools.ietf.org/html/rfc5763#section-5: [t]he
        // endpoint that is the offerer MUST use the setup attribute value of
        // setup:actpass and be prepared to receive a client_hello before it
        // receives the answer. The answerer MUST use either a setup attribute
        // value of setup:active or setup:passive.
        dtlsControl.setSetup(
                controlling
                    ? DtlsControl.Setup.ACTPASS
                    : DtlsControl.Setup.ACTIVE);
        // XXX For DTLS, the media type doesn't matter (as long as it's not
        // null).
        // XXX The actual start of the DTLS servers/clients will be delayed
        // until an rtpConnector is set (when a MediaStream with this
        // SrtpControl starts or is assigned a target).
        dtlsControl.start(MediaType.AUDIO);
        return dtlsControl;
    }

    /**
     * Initializes a new <tt>Agent</tt> instance which implements the ICE
     * protocol and which is to be used by this instance to implement the Jingle
     * ICE-UDP transport.
     *
     * @param controlling
     * @param iceStreamName
     * @param rtcpmux
     * @return a new <tt>Agent</tt> instance which implements the ICE protocol
     * and which is to be used by this instance to implement the Jingle ICE-UDP
     * transport
     * @throws IOException if initializing a new <tt>Agent</tt> instance for the
     * purposes of this <tt>TransportManager</tt> fails
     */
    private Agent createIceAgent(boolean controlling,
                                 String iceStreamName,
                                 boolean rtcpmux)
            throws IOException
    {
        NetworkAddressManagerService nams
            = ServiceUtils.getService(
                    getBundleContext(),
                    NetworkAddressManagerService.class);
        Agent iceAgent = new Agent(logger.getLevel(), iceUfragPrefix);

        //add videobridge specific harvesters such as a mapping and an Amazon
        //AWS EC2 harvester
        appendVideobridgeHarvesters(iceAgent, rtcpmux);
{'fraction_non_alphanumeric': 0.08120729106673101, 'fraction_numerical': 0.002023566769920458, 'mean_word_length': 1.7212809217214504, 'pattern_counts': {'":': 4, '<': 281, '<?xml version=': 0, '>': 277, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 40, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7320330', 'n_tokens_mistral': 16704, 'n_tokens_neox': 15873, 'n_words': 4636}