MediaEndpointPeerConnection.cpp   [plain text]


/*
 * Copyright (C) 2015 Ericsson AB. All rights reserved.
 * Copyright (C) 2017 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of Ericsson nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "MediaEndpointPeerConnection.h"

#if ENABLE(WEB_RTC)

#include "EventNames.h"
#include "JSRTCSessionDescription.h"
#include "MediaEndpointSessionConfiguration.h"
#include "MediaEndpointSessionDescription.h"
#include "MediaStream.h"
#include "MediaStreamEvent.h"
#include "MediaStreamTrack.h"
#include "NotImplemented.h"
#include "PeerMediaDescription.h"
#include "RTCAnswerOptions.h"
#include "RTCConfiguration.h"
#include "RTCIceCandidate.h"
#include "RTCOfferOptions.h"
#include "RTCPeerConnection.h"
#include "RTCPeerConnectionIceEvent.h"
#include "RTCRtpTransceiver.h"
#include "RTCTrackEvent.h"
#include "SDPProcessor.h"
#include <wtf/Function.h>
#include <wtf/MainThread.h>
#include <wtf/text/Base64.h>

namespace WebCore {

using namespace PeerConnection;

using MediaDescriptionVector = Vector<PeerMediaDescription>;
using RtpTransceiverVector = Vector<RefPtr<RTCRtpTransceiver>>;

// We use base64 to generate the random strings so we need a size that avoids padding to get ice-chars.
static const size_t cnameSize = 18;
// Size range from 4 to 256 ice-chars defined in RFC 5245.
static const size_t iceUfragSize = 6;
// Size range from 22 to 256 ice-chars defined in RFC 5245.
static const size_t icePasswordSize = 24;

#if !USE(LIBWEBRTC)
static std::unique_ptr<PeerConnectionBackend> createMediaEndpointPeerConnection(RTCPeerConnection& peerConnection)
{
    return std::unique_ptr<PeerConnectionBackend>(new MediaEndpointPeerConnection(peerConnection));
}

CreatePeerConnectionBackend PeerConnectionBackend::create = createMediaEndpointPeerConnection;
#endif

static String randomString(size_t size)
{
    unsigned char randomValues[size];
    cryptographicallyRandomValues(randomValues, size);
    return base64Encode(randomValues, size);
}

MediaEndpointPeerConnection::MediaEndpointPeerConnection(RTCPeerConnection& peerConnection)
    : PeerConnectionBackend(peerConnection)
    , m_mediaEndpoint(MediaEndpoint::create(*this))
    , m_sdpProcessor(std::make_unique<SDPProcessor>(m_peerConnection.scriptExecutionContext()))
    , m_cname(randomString(cnameSize))
    , m_iceUfrag(randomString(iceUfragSize))
    , m_icePassword(randomString(icePasswordSize))
{
    ASSERT(m_mediaEndpoint);

    m_defaultAudioPayloads = m_mediaEndpoint->getDefaultAudioPayloads();
    m_defaultVideoPayloads = m_mediaEndpoint->getDefaultVideoPayloads();

    // Tasks (see runTask()) will be deferred until we get the DTLS fingerprint.
    m_mediaEndpoint->generateDtlsInfo();
}

static RTCRtpTransceiver* matchTransceiver(const RtpTransceiverVector& transceivers, const WTF::Function<bool(RTCRtpTransceiver&)>& matchFunction)
{
    for (auto& transceiver : transceivers) {
        if (matchFunction(*transceiver))
            return transceiver.get();
    }
    return nullptr;
}

static RTCRtpTransceiver* matchTransceiverByMid(const RtpTransceiverVector& transceivers, const String& mid)
{
    return matchTransceiver(transceivers, [&mid] (RTCRtpTransceiver& current) {
        return current.mid() == mid;
    });
}

static bool hasUnassociatedTransceivers(const RtpTransceiverVector& transceivers)
{
    return matchTransceiver(transceivers, [] (RTCRtpTransceiver& current) {
        return current.mid().isNull() && !current.stopped();
    });
}

void MediaEndpointPeerConnection::runTask(Function<void ()>&& task)
{
    if (m_dtlsFingerprint.isNull()) {
        // Only one task needs to be deferred since it will hold off any others until completed.
        ASSERT(!m_initialDeferredTask);
        m_initialDeferredTask = WTFMove(task);
    } else
        callOnMainThread(WTFMove(task));
}

void MediaEndpointPeerConnection::startRunningTasks()
{
    if (!m_initialDeferredTask)
        return;

    m_initialDeferredTask();
    m_initialDeferredTask = nullptr;
}

void MediaEndpointPeerConnection::doCreateOffer(RTCOfferOptions&& options)
{
    runTask([this, protectedOptions = WTFMove(options)]() mutable {
        createOfferTask(protectedOptions);
    });
}

void MediaEndpointPeerConnection::createOfferTask(const RTCOfferOptions&)
{
    ASSERT(!m_dtlsFingerprint.isEmpty());

    MediaEndpointSessionDescription* localDescription = internalLocalDescription();
    RefPtr<MediaEndpointSessionConfiguration> configurationSnapshot = localDescription ?
        localDescription->configuration()->clone() : MediaEndpointSessionConfiguration::create();

    configurationSnapshot->setBundlePolicy(m_peerConnection.getConfiguration().bundlePolicy);
    configurationSnapshot->setSessionVersion(m_sdpOfferSessionVersion++);

    auto transceivers = RtpTransceiverVector(m_peerConnection.getTransceivers());

    // Remove any transceiver objects from transceivers that can be matched to an existing media description.
    for (auto& mediaDescription : configurationSnapshot->mediaDescriptions()) {
        if (!mediaDescription.port) {
            // This media description should be recycled.
            continue;
        }

        RTCRtpTransceiver* transceiver = matchTransceiverByMid(transceivers, mediaDescription.mid);
        if (!transceiver)
            continue;

        mediaDescription.mode = transceiver->directionString();
        if (transceiver->hasSendingDirection()) {
            auto& sender = transceiver->sender();

            mediaDescription.mediaStreamId = sender.mediaStreamIds()[0];
            mediaDescription.mediaStreamTrackId = sender.trackId();
        }

        transceivers.removeFirst(transceiver);
    }

    // Add media descriptions for remaining transceivers.
    for (auto& transceiver : transceivers) {
        PeerMediaDescription mediaDescription;
        auto& sender = transceiver->sender();

        mediaDescription.mode = transceiver->directionString();
        mediaDescription.mid = transceiver->provisionalMid();
        mediaDescription.mediaStreamId = sender.mediaStreamIds()[0];
        mediaDescription.type = sender.trackKind();
        mediaDescription.payloads = sender.trackKind() == "audio" ? m_defaultAudioPayloads : m_defaultVideoPayloads;
        mediaDescription.dtlsFingerprintHashFunction = m_dtlsFingerprintFunction;
        mediaDescription.dtlsFingerprint = m_dtlsFingerprint;
        mediaDescription.cname = m_cname;
        mediaDescription.addSsrc(cryptographicallyRandomNumber());
        mediaDescription.iceUfrag = m_iceUfrag;
        mediaDescription.icePassword = m_icePassword;

        if (sender.track())
            mediaDescription.mediaStreamTrackId = sender.trackId();

        configurationSnapshot->addMediaDescription(WTFMove(mediaDescription));
    }

    String sdp;
    SDPProcessor::Result result = m_sdpProcessor->generate(*configurationSnapshot, sdp);
    if (result != SDPProcessor::Result::Success) {
        createOfferFailed(Exception { OperationError, "SDPProcessor internal error" });
        return;
    }
    createOfferSucceeded(WTFMove(sdp));
}

void MediaEndpointPeerConnection::doCreateAnswer(RTCAnswerOptions&& options)
{
    runTask([this, protectedOptions = WTFMove(options)]() mutable {
        createAnswerTask(protectedOptions);
    });
}

void MediaEndpointPeerConnection::createAnswerTask(const RTCAnswerOptions&)
{
    ASSERT(!m_dtlsFingerprint.isEmpty());

    if (!internalRemoteDescription()) {
        createAnswerFailed(Exception { INVALID_STATE_ERR, "No remote description set" });
        return;
    }

    MediaEndpointSessionDescription* localDescription = internalLocalDescription();
    RefPtr<MediaEndpointSessionConfiguration> configurationSnapshot = localDescription ?
        localDescription->configuration()->clone() : MediaEndpointSessionConfiguration::create();

    configurationSnapshot->setBundlePolicy(m_peerConnection.getConfiguration().bundlePolicy);
    configurationSnapshot->setSessionVersion(m_sdpAnswerSessionVersion++);

    auto transceivers = RtpTransceiverVector(m_peerConnection.getTransceivers());
    auto& remoteMediaDescriptions = internalRemoteDescription()->configuration()->mediaDescriptions();

    for (unsigned i = 0; i < remoteMediaDescriptions.size(); ++i) {
        auto& remoteMediaDescription = remoteMediaDescriptions[i];

        auto* transceiver = matchTransceiverByMid(transceivers, remoteMediaDescription.mid);
        if (!transceiver) {
            LOG_ERROR("Could not find a matching transceiver for remote description while creating answer");
            continue;
        }

        if (i >= configurationSnapshot->mediaDescriptions().size()) {
            PeerMediaDescription newMediaDescription;

            auto& sender = transceiver->sender();
            if (sender.track()) {
                if (sender.mediaStreamIds().size())
                    newMediaDescription.mediaStreamId = sender.mediaStreamIds()[0];
                newMediaDescription.mediaStreamTrackId = sender.trackId();
                newMediaDescription.addSsrc(cryptographicallyRandomNumber());
            }

            newMediaDescription.mode = transceiver->directionString();
            newMediaDescription.type = remoteMediaDescription.type;
            newMediaDescription.mid = remoteMediaDescription.mid;
            newMediaDescription.dtlsSetup = remoteMediaDescription.dtlsSetup == "active" ? "passive" : "active";
            newMediaDescription.dtlsFingerprintHashFunction = m_dtlsFingerprintFunction;
            newMediaDescription.dtlsFingerprint = m_dtlsFingerprint;
            newMediaDescription.cname = m_cname;
            newMediaDescription.iceUfrag = m_iceUfrag;
            newMediaDescription.icePassword = m_icePassword;

            configurationSnapshot->addMediaDescription(WTFMove(newMediaDescription));
        }

        PeerMediaDescription& localMediaDescription = configurationSnapshot->mediaDescriptions()[i];

        localMediaDescription.payloads = remoteMediaDescription.payloads;
        localMediaDescription.rtcpMux = remoteMediaDescription.rtcpMux;

        if (!localMediaDescription.ssrcs.size())
            localMediaDescription.addSsrc(cryptographicallyRandomNumber());

        if (localMediaDescription.dtlsSetup == "actpass")
            localMediaDescription.dtlsSetup = "passive";

        transceivers.removeFirst(transceiver);
    }

    // Unassociated (non-stopped) transceivers need to be negotiated in a follow-up offer.
    if (hasUnassociatedTransceivers(transceivers))
        markAsNeedingNegotiation();

    String sdp;
    SDPProcessor::Result result = m_sdpProcessor->generate(*configurationSnapshot, sdp);
    if (result != SDPProcessor::Result::Success) {
        createAnswerFailed(Exception { OperationError, "SDPProcessor internal error" });
        return;
    }
    createAnswerSucceeded(WTFMove(sdp));
}

static RealtimeMediaSourceMap createSourceMap(const MediaDescriptionVector& remoteMediaDescriptions, unsigned localMediaDescriptionCount, const RtpTransceiverVector& transceivers)
{
    RealtimeMediaSourceMap sourceMap;

    for (unsigned i = 0; i < remoteMediaDescriptions.size() && i < localMediaDescriptionCount; ++i) {
        auto& remoteMediaDescription = remoteMediaDescriptions[i];
        if (remoteMediaDescription.type != "audio" && remoteMediaDescription.type != "video")
            continue;

        RTCRtpTransceiver* transceiver = matchTransceiverByMid(transceivers, remoteMediaDescription.mid);
        if (transceiver) {
            if (transceiver->hasSendingDirection() && transceiver->sender().track())
                sourceMap.set(transceiver->mid(), &transceiver->sender().track()->source());
        }
    }

    return sourceMap;
}

void MediaEndpointPeerConnection::doSetLocalDescription(RTCSessionDescription& description)
{
    runTask([this, protectedDescription = RefPtr<RTCSessionDescription>(&description)]() mutable {
        setLocalDescriptionTask(WTFMove(protectedDescription));
    });
}

void MediaEndpointPeerConnection::setLocalDescriptionTask(RefPtr<RTCSessionDescription>&& description)
{
    if (m_peerConnection.isClosed())
        return;

    auto result = MediaEndpointSessionDescription::create(WTFMove(description), *m_sdpProcessor);
    if (result.hasException()) {
        setLocalDescriptionFailed(result.releaseException());
        return;
    }
    auto newDescription = result.releaseReturnValue();

    const RtpTransceiverVector& transceivers = m_peerConnection.getTransceivers();
    const MediaDescriptionVector& mediaDescriptions = newDescription->configuration()->mediaDescriptions();
    MediaEndpointSessionDescription* localDescription = internalLocalDescription();
    unsigned previousNumberOfMediaDescriptions = localDescription ? localDescription->configuration()->mediaDescriptions().size() : 0;
    bool hasNewMediaDescriptions = mediaDescriptions.size() > previousNumberOfMediaDescriptions;
    bool isInitiator = newDescription->type() == RTCSdpType::Offer;

    if (hasNewMediaDescriptions) {
        MediaEndpoint::UpdateResult result = m_mediaEndpoint->updateReceiveConfiguration(newDescription->configuration(), isInitiator);

        if (result == MediaEndpoint::UpdateResult::SuccessWithIceRestart) {
            if (m_peerConnection.iceGatheringState() != RTCIceGatheringState::Gathering)
                m_peerConnection.updateIceGatheringState(RTCIceGatheringState::Gathering);

            if (m_peerConnection.iceConnectionState() != RTCIceConnectionState::Completed)
                m_peerConnection.updateIceConnectionState(RTCIceConnectionState::Connected);

            LOG_ERROR("ICE restart is not implemented");
            notImplemented();

        } else if (result == MediaEndpoint::UpdateResult::Failed) {
            setLocalDescriptionFailed(Exception { OperationError, "Unable to apply session description" });
            return;
        }

        // Associate media descriptions with transceivers (set provisional mid to 'final' mid).
        for (auto& mediaDescription : mediaDescriptions) {
            RTCRtpTransceiver* transceiver = matchTransceiver(transceivers, [&mediaDescription] (RTCRtpTransceiver& current) {
                return current.provisionalMid() == mediaDescription.mid;
            });
            if (transceiver)
                transceiver->setMid(transceiver->provisionalMid());
        }
    }

    if (internalRemoteDescription()) {
        MediaEndpointSessionConfiguration* remoteConfiguration = internalRemoteDescription()->configuration();
        RealtimeMediaSourceMap sendSourceMap = createSourceMap(remoteConfiguration->mediaDescriptions(), mediaDescriptions.size(), transceivers);

        if (m_mediaEndpoint->updateSendConfiguration(remoteConfiguration, sendSourceMap, isInitiator) == MediaEndpoint::UpdateResult::Failed) {
            setLocalDescriptionFailed(Exception { OperationError, "Unable to apply session description" });
            return;
        }
    }

    if (!hasUnassociatedTransceivers(transceivers))
        clearNegotiationNeededState();

    RTCSignalingState newSignalingState;

    // Update state and local descriptions according to setLocal/RemoteDescription processing model
    switch (newDescription->type()) {
    case RTCSdpType::Offer:
        m_pendingLocalDescription = WTFMove(newDescription);
        newSignalingState = RTCSignalingState::HaveLocalOffer;
        break;

    case RTCSdpType::Answer:
        m_currentLocalDescription = WTFMove(newDescription);
        m_currentRemoteDescription = m_pendingRemoteDescription;
        m_pendingLocalDescription = nullptr;
        m_pendingRemoteDescription = nullptr;
        newSignalingState = RTCSignalingState::Stable;
        break;

    case RTCSdpType::Rollback:
        m_pendingLocalDescription = nullptr;
        newSignalingState = RTCSignalingState::Stable;
        break;

    case RTCSdpType::Pranswer:
        m_pendingLocalDescription = WTFMove(newDescription);
        newSignalingState = RTCSignalingState::HaveLocalPranswer;
        break;
    }

    updateSignalingState(newSignalingState);

    if (m_peerConnection.iceGatheringState() == RTCIceGatheringState::New && mediaDescriptions.size())
        m_peerConnection.updateIceGatheringState(RTCIceGatheringState::Gathering);

    markAsNeedingNegotiation();
    setLocalDescriptionSucceeded();
}

RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::localDescription() const
{
    return createRTCSessionDescription(internalLocalDescription());
}

RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::currentLocalDescription() const
{
    return createRTCSessionDescription(m_currentLocalDescription.get());
}

RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::pendingLocalDescription() const
{
    return createRTCSessionDescription(m_pendingLocalDescription.get());
}

void MediaEndpointPeerConnection::doSetRemoteDescription(RTCSessionDescription& description)
{
    runTask([this, protectedDescription = RefPtr<RTCSessionDescription>(&description)]() mutable {
        setRemoteDescriptionTask(WTFMove(protectedDescription));
    });
}

void MediaEndpointPeerConnection::setRemoteDescriptionTask(RefPtr<RTCSessionDescription>&& description)
{
    auto result = MediaEndpointSessionDescription::create(WTFMove(description), *m_sdpProcessor);
    if (result.hasException()) {
        setRemoteDescriptionFailed(result.releaseException());
        return;
    }
    auto newDescription = result.releaseReturnValue();

    auto& mediaDescriptions = newDescription->configuration()->mediaDescriptions();
    for (auto& mediaDescription : mediaDescriptions) {
        if (mediaDescription.type != "audio" && mediaDescription.type != "video")
            continue;

        mediaDescription.payloads = m_mediaEndpoint->filterPayloads(mediaDescription.payloads, mediaDescription.type == "audio" ? m_defaultAudioPayloads : m_defaultVideoPayloads);
    }

    bool isInitiator = newDescription->type() == RTCSdpType::Answer;
    const RtpTransceiverVector& transceivers = m_peerConnection.getTransceivers();

    RealtimeMediaSourceMap sendSourceMap;
    if (internalLocalDescription())
        sendSourceMap = createSourceMap(mediaDescriptions, internalLocalDescription()->configuration()->mediaDescriptions().size(), transceivers);

    if (m_mediaEndpoint->updateSendConfiguration(newDescription->configuration(), sendSourceMap, isInitiator) == MediaEndpoint::UpdateResult::Failed) {
        setRemoteDescriptionFailed(Exception { OperationError, "Unable to apply session description" });
        return;
    }

    // One legacy MediaStreamEvent will be fired for every new MediaStream created as this remote description is set.
    Vector<RefPtr<MediaStreamEvent>> legacyMediaStreamEvents;

    for (auto& mediaDescription : mediaDescriptions) {
        RTCRtpTransceiver* transceiver = matchTransceiverByMid(transceivers, mediaDescription.mid);
        if (!transceiver) {
            bool receiveOnlyFlag = false;

            if (mediaDescription.mode == "sendrecv" || mediaDescription.mode == "recvonly") {
                // Try to match an existing transceiver.
                transceiver = matchTransceiver(transceivers, [&mediaDescription] (RTCRtpTransceiver& current) {
                    return !current.stopped() && current.mid().isNull() && current.sender().trackKind() == mediaDescription.type;
                });

                if (transceiver) {
                    // This transceiver was created locally with a provisional mid. Its real mid will now be set by the remote
                    // description so we need to update the mid of the transceiver's muted source to preserve the association.
                    transceiver->setMid(mediaDescription.mid);
                    m_mediaEndpoint->replaceMutedRemoteSourceMid(transceiver->provisionalMid(), mediaDescription.mid);
                } else
                    receiveOnlyFlag = true;
            }

            if (!transceiver) {
                auto sender = RTCRtpSender::create(String(mediaDescription.type), Vector<String>(), m_peerConnection.senderBackend());
                auto receiver = createReceiver(mediaDescription.mid, mediaDescription.type, mediaDescription.mediaStreamTrackId);

                auto newTransceiver = RTCRtpTransceiver::create(WTFMove(sender), WTFMove(receiver));
                newTransceiver->setMid(mediaDescription.mid);
                if (receiveOnlyFlag)
                    newTransceiver->disableSendingDirection();

                transceiver = newTransceiver.ptr();
                m_peerConnection.addTransceiver(WTFMove(newTransceiver));
            }
        }

        if (mediaDescription.mode == "sendrecv" || mediaDescription.mode == "sendonly") {
            auto& receiver = transceiver->receiver();
            if (receiver.isDispatched())
                continue;
            receiver.setDispatched(true);

            Vector<String> mediaStreamIds;
            if (!mediaDescription.mediaStreamId.isEmpty())
                mediaStreamIds.append(mediaDescription.mediaStreamId);

            // A remote track can be associated with 0..* MediaStreams. We create a new stream for
            // a track in case of an unrecognized stream id, or just add the track if the stream
            // already exists.
            HashMap<String, RefPtr<MediaStream>> trackEventMediaStreams;
            for (auto& id : mediaStreamIds) {
                if (m_remoteStreamMap.contains(id)) {
                    RefPtr<MediaStream> stream = m_remoteStreamMap.get(id);
                    stream->addTrack(*receiver.track());
                    trackEventMediaStreams.add(id, WTFMove(stream));
                } else {
                    auto newStream = MediaStream::create(*m_peerConnection.scriptExecutionContext(), MediaStreamTrackVector({ receiver.track() }));
                    m_remoteStreamMap.add(id, newStream.copyRef());
                    legacyMediaStreamEvents.append(MediaStreamEvent::create(eventNames().addstreamEvent, false, false, newStream.copyRef()));
                    trackEventMediaStreams.add(id, WTFMove(newStream));
                }
            }

            Vector<RefPtr<MediaStream>> streams;
            copyValuesToVector(trackEventMediaStreams, streams);

            m_peerConnection.fireEvent(RTCTrackEvent::create(eventNames().trackEvent, false, false,
                &receiver, receiver.track(), WTFMove(streams), transceiver));
        }
    }

    // Fire legacy addstream events.
    for (auto& event : legacyMediaStreamEvents)
        m_peerConnection.fireEvent(*event);

    RTCSignalingState newSignalingState;

    // Update state and local descriptions according to setLocal/RemoteDescription processing model
    switch (newDescription->type()) {
    case RTCSdpType::Offer:
        m_pendingRemoteDescription = WTFMove(newDescription);
        newSignalingState = RTCSignalingState::HaveRemoteOffer;
        break;

    case RTCSdpType::Answer:
        m_currentRemoteDescription = WTFMove(newDescription);
        m_currentLocalDescription = m_pendingLocalDescription;
        m_pendingRemoteDescription = nullptr;
        m_pendingLocalDescription = nullptr;
        newSignalingState = RTCSignalingState::Stable;
        break;

    case RTCSdpType::Rollback:
        m_pendingRemoteDescription = nullptr;
        newSignalingState = RTCSignalingState::Stable;
        break;

    case RTCSdpType::Pranswer:
        m_pendingRemoteDescription = WTFMove(newDescription);
        newSignalingState = RTCSignalingState::HaveRemotePranswer;
        break;
    }

    updateSignalingState(newSignalingState);
    setRemoteDescriptionSucceeded();
}

RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::remoteDescription() const
{
    return createRTCSessionDescription(internalRemoteDescription());
}

RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::currentRemoteDescription() const
{
    return createRTCSessionDescription(m_currentRemoteDescription.get());
}

RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::pendingRemoteDescription() const
{
    return createRTCSessionDescription(m_pendingRemoteDescription.get());
}

bool MediaEndpointPeerConnection::setConfiguration(MediaEndpointConfiguration&& configuration)
{
    m_mediaEndpoint->setConfiguration(WTFMove(configuration));
    return true;
}

void MediaEndpointPeerConnection::doAddIceCandidate(RTCIceCandidate& rtcCandidate)
{
    runTask([this, protectedCandidate = RefPtr<RTCIceCandidate>(&rtcCandidate)]() mutable {
        addIceCandidateTask(*protectedCandidate);
    });
}

void MediaEndpointPeerConnection::addIceCandidateTask(RTCIceCandidate& rtcCandidate)
{
    if (!internalRemoteDescription()) {
        addIceCandidateFailed(Exception { INVALID_STATE_ERR, "No remote description set" });
        return;
    }

    auto& remoteMediaDescriptions = internalRemoteDescription()->configuration()->mediaDescriptions();
    PeerMediaDescription* targetMediaDescription = nullptr;

    // When identifying the target media description, sdpMid takes precedence over sdpMLineIndex
    // if both are present.
    if (!rtcCandidate.sdpMid().isNull()) {
        const String& mid = rtcCandidate.sdpMid();
        for (auto& description : remoteMediaDescriptions) {
            if (description.mid == mid) {
                targetMediaDescription = &description;
                break;
            }
        }

        if (!targetMediaDescription) {
            addIceCandidateFailed(Exception { OperationError, "sdpMid did not match any media description" });
            return;
        }
    } else if (rtcCandidate.sdpMLineIndex()) {
        unsigned short sdpMLineIndex = rtcCandidate.sdpMLineIndex().value();
        if (sdpMLineIndex >= remoteMediaDescriptions.size()) {
            addIceCandidateFailed(Exception { OperationError, "sdpMLineIndex is out of range" });
            return;
        }
        targetMediaDescription = &remoteMediaDescriptions[sdpMLineIndex];
    } else {
        ASSERT_NOT_REACHED();
        return;
    }

    auto result = m_sdpProcessor->parseCandidateLine(rtcCandidate.candidate());
    if (result.parsingStatus() != SDPProcessor::Result::Success) {
        if (result.parsingStatus() == SDPProcessor::Result::ParseError)
            addIceCandidateFailed(Exception { OperationError, "Invalid candidate content" });
        else
            LOG_ERROR("SDPProcessor internal error");
        return;
    }

    ASSERT(targetMediaDescription);
    m_mediaEndpoint->addRemoteCandidate(result.candidate(), targetMediaDescription->mid, targetMediaDescription->iceUfrag, targetMediaDescription->icePassword);

    targetMediaDescription->addIceCandidate(WTFMove(result.candidate()));

    addIceCandidateSucceeded();
}

void MediaEndpointPeerConnection::getStats(MediaStreamTrack* track, Ref<DeferredPromise>&& promise)
{
    m_mediaEndpoint->getStats(track, WTFMove(promise));
}

Vector<RefPtr<MediaStream>> MediaEndpointPeerConnection::getRemoteStreams() const
{
    Vector<RefPtr<MediaStream>> remoteStreams;
    copyValuesToVector(m_remoteStreamMap, remoteStreams);
    return remoteStreams;
}

Ref<RTCRtpReceiver> MediaEndpointPeerConnection::createReceiver(const String& transceiverMid, const String& trackKind, const String& trackId)
{
    RealtimeMediaSource::Type sourceType = trackKind == "audio" ? RealtimeMediaSource::Type::Audio : RealtimeMediaSource::Type::Video;

    // Create a muted remote source that will be unmuted once media starts arriving.
    auto remoteSource = m_mediaEndpoint->createMutedRemoteSource(transceiverMid, sourceType);
    auto remoteTrackPrivate = MediaStreamTrackPrivate::create(WTFMove(remoteSource), String(trackId));
    auto remoteTrack = MediaStreamTrack::create(*m_peerConnection.scriptExecutionContext(), WTFMove(remoteTrackPrivate));

    return RTCRtpReceiver::create(WTFMove(remoteTrack));
}

std::unique_ptr<RTCDataChannelHandler> MediaEndpointPeerConnection::createDataChannelHandler(const String& label, const RTCDataChannelInit& options)
{
    return m_mediaEndpoint->createDataChannelHandler(label, options);
}

void MediaEndpointPeerConnection::replaceTrack(RTCRtpSender& sender, Ref<MediaStreamTrack>&& withTrack, DOMPromiseDeferred<void>&& promise)
{
    RTCRtpTransceiver* transceiver = matchTransceiver(m_peerConnection.getTransceivers(), [&sender] (RTCRtpTransceiver& current) {
        return &current.sender() == &sender;
    });
    ASSERT(transceiver);

    const String& mid = transceiver->mid();
    if (mid.isNull()) {
        // Transceiver is not associated with a media description yet.
        sender.setTrack(WTFMove(withTrack));
        promise.resolve();
        return;
    }

    runTask([this, protectedSender = RefPtr<RTCRtpSender>(&sender), mid, protectedTrack = WTFMove(withTrack), protectedPromise = WTFMove(promise)]() mutable {
        replaceTrackTask(*protectedSender, mid, WTFMove(protectedTrack), protectedPromise);
    });
}

void MediaEndpointPeerConnection::replaceTrackTask(RTCRtpSender& sender, const String& mid, Ref<MediaStreamTrack>&& withTrack, DOMPromiseDeferred<void>& promise)
{
    if (m_peerConnection.isClosed())
        return;

    m_mediaEndpoint->replaceSendSource(withTrack->source(), mid);

    sender.setTrack(WTFMove(withTrack));
    promise.resolve();
}

void MediaEndpointPeerConnection::doStop()
{
    m_mediaEndpoint->stop();
}

void MediaEndpointPeerConnection::emulatePlatformEvent(const String& action)
{
    m_mediaEndpoint->emulatePlatformEvent(action);
}

MediaEndpointSessionDescription* MediaEndpointPeerConnection::internalLocalDescription() const
{
    return m_pendingLocalDescription ? m_pendingLocalDescription.get() : m_currentLocalDescription.get();
}

MediaEndpointSessionDescription* MediaEndpointPeerConnection::internalRemoteDescription() const
{
    return m_pendingRemoteDescription ? m_pendingRemoteDescription.get() : m_currentRemoteDescription.get();
}

RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::createRTCSessionDescription(MediaEndpointSessionDescription* description) const
{
    return description ? description->toRTCSessionDescription(*m_sdpProcessor) : nullptr;
}

void MediaEndpointPeerConnection::gotDtlsFingerprint(const String& fingerprint, const String& fingerprintFunction)
{
    ASSERT(isMainThread());

    m_dtlsFingerprint = fingerprint;
    m_dtlsFingerprintFunction = fingerprintFunction;

    startRunningTasks();
}

void MediaEndpointPeerConnection::gotIceCandidate(const String& mid, IceCandidate&& candidate)
{
    ASSERT(isMainThread());

    auto& mediaDescriptions = internalLocalDescription()->configuration()->mediaDescriptions();
    size_t mediaDescriptionIndex = notFound;

    for (size_t i = 0; i < mediaDescriptions.size(); ++i) {
        if (mediaDescriptions[i].mid == mid) {
            mediaDescriptionIndex = i;
            break;
        }
    }
    ASSERT(mediaDescriptionIndex != notFound);

    String candidateLine;
    auto result = m_sdpProcessor->generateCandidateLine(candidate, candidateLine);
    if (result != SDPProcessor::Result::Success) {
        LOG_ERROR("SDPProcessor internal error");
        return;
    }

    mediaDescriptions[mediaDescriptionIndex].addIceCandidate(WTFMove(candidate));

    fireICECandidateEvent(RTCIceCandidate::create(candidateLine, mid, mediaDescriptionIndex));
}

void MediaEndpointPeerConnection::doneGatheringCandidates(const String& mid)
{
    ASSERT(isMainThread());

    RtpTransceiverVector transceivers = RtpTransceiverVector(m_peerConnection.getTransceivers());
    RTCRtpTransceiver* notifyingTransceiver = matchTransceiverByMid(transceivers, mid);
    ASSERT(notifyingTransceiver);

    notifyingTransceiver->iceTransport().setGatheringState(RTCIceGatheringState::Complete);

    // Don't notify the script if there are transceivers still gathering.
    RTCRtpTransceiver* stillGatheringTransceiver = matchTransceiver(transceivers, [] (RTCRtpTransceiver& current) {
        return !current.stopped() && !current.mid().isNull()
            && current.iceTransport().gatheringState() != RTCIceGatheringState::Complete;
    });
    if (!stillGatheringTransceiver)
        PeerConnectionBackend::doneGatheringCandidates();
}

static RTCIceConnectionState deriveAggregatedIceConnectionState(const Vector<RTCIceTransportState>& states)
{
    unsigned newCount = 0;
    unsigned checkingCount = 0;
    unsigned connectedCount = 0;
    unsigned completedCount = 0;
    unsigned failedCount = 0;
    unsigned disconnectedCount = 0;
    unsigned closedCount = 0;

    for (auto& state : states) {
        switch (state) {
        case RTCIceTransportState::New:
            ++newCount;
            break;
        case RTCIceTransportState::Checking:
            ++checkingCount;
            break;
        case RTCIceTransportState::Connected:
            ++connectedCount;
            break;
        case RTCIceTransportState::Completed:
            ++completedCount;
            break;
        case RTCIceTransportState::Failed:
            ++failedCount;
            break;
        case RTCIceTransportState::Disconnected:
            ++disconnectedCount;
            break;
        case RTCIceTransportState::Closed:
            ++closedCount;
            break;
        }
    }

    // The aggregated RTCIceConnectionState is derived from the RTCIceTransportState of all RTCIceTransports.
    if ((newCount > 0 && !checkingCount && !failedCount && !disconnectedCount) || (closedCount == states.size()))
        return RTCIceConnectionState::New;

    if (checkingCount > 0 && !failedCount && !disconnectedCount)
        return RTCIceConnectionState::Checking;

    if ((connectedCount + completedCount + closedCount) == states.size() && connectedCount > 0)
        return RTCIceConnectionState::Connected;

    if ((completedCount + closedCount) == states.size() && completedCount > 0)
        return RTCIceConnectionState::Completed;

    if (failedCount > 0)
        return RTCIceConnectionState::Failed;

    if (disconnectedCount > 0) // Any failed caught above.
        return RTCIceConnectionState::Disconnected;

    ASSERT_NOT_REACHED();
    return RTCIceConnectionState::New;
}

void MediaEndpointPeerConnection::iceTransportStateChanged(const String& mid, RTCIceTransportState mediaEndpointIceTransportState)
{
    ASSERT(isMainThread());

    RTCRtpTransceiver* transceiver = matchTransceiverByMid(m_peerConnection.getTransceivers(), mid);
    ASSERT(transceiver);

    RTCIceTransportState transportState = static_cast<RTCIceTransportState>(mediaEndpointIceTransportState);
    transceiver->iceTransport().setState(transportState);

    // Determine if the script needs to be notified.
    Vector<RTCIceTransportState> transportStates;
    transportStates.reserveInitialCapacity(m_peerConnection.getTransceivers().size());
    for (auto& transceiver : m_peerConnection.getTransceivers())
        transportStates.uncheckedAppend(transceiver->iceTransport().state());

    m_peerConnection.updateIceConnectionState(deriveAggregatedIceConnectionState(transportStates));
}

} // namespace WebCore

#endif // ENABLE(WEB_RTC)