WebRTC: Imlement MediaEndpointPeerConnection::setLocalDescription()
authoradam.bergkvist@ericsson.com <adam.bergkvist@ericsson.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 8 Jun 2016 07:37:08 +0000 (07:37 +0000)
committeradam.bergkvist@ericsson.com <adam.bergkvist@ericsson.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 8 Jun 2016 07:37:08 +0000 (07:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=158190

Reviewed by Eric Carlson.

Source/WebCore:

Add implementation for MediaEndpointPeerConnection::setLocalDescription. This function
parses the input SDP, configures the media backend and updates the
RTCPeerConnection state.

This change adds MediaEndpointSessionDescription which is an object representation
of an RTCSessionDescription (which contains an SDP string).

Test: fast/mediastream/RTCPeerConnection-setLocalDescription-offer.html

* CMakeLists.txt:
Add MediaEndpointSessionDescription.
* Modules/mediastream/MediaEndpointPeerConnection.cpp:
(WebCore::hasUnassociatedTransceivers):
(WebCore::MediaEndpointPeerConnection::createOfferTask):
(WebCore::MediaEndpointPeerConnection::setLocalDescription):
(WebCore::MediaEndpointPeerConnection::setLocalDescriptionTask):
Add implementation.
(WebCore::MediaEndpointPeerConnection::localDescription):
(WebCore::MediaEndpointPeerConnection::currentLocalDescription):
(WebCore::MediaEndpointPeerConnection::pendingLocalDescription):
(WebCore::MediaEndpointPeerConnection::localDescriptionTypeValidForState):
(WebCore::MediaEndpointPeerConnection::internalLocalDescription):
(WebCore::MediaEndpointPeerConnection::createRTCSessionDescription):
* Modules/mediastream/MediaEndpointPeerConnection.h:
* Modules/mediastream/MediaEndpointSessionDescription.cpp: Added.
(WebCore::MediaEndpointSessionDescription::create):
(WebCore::MediaEndpointSessionDescription::toRTCSessionDescription):
(WebCore::MediaEndpointSessionDescription::typeString):
(WebCore::MediaEndpointSessionDescription::isLaterThan):
* Modules/mediastream/MediaEndpointSessionDescription.h: Added.
(WebCore::MediaEndpointSessionDescription::~MediaEndpointSessionDescription):
(WebCore::MediaEndpointSessionDescription::type):
(WebCore::MediaEndpointSessionDescription::configuration):
(WebCore::MediaEndpointSessionDescription::MediaEndpointSessionDescription):
* WebCore.xcodeproj/project.pbxproj:
Add MediaEndpointSessionDescription.

LayoutTests:

Add new test for RTCPeerConnection.setLocalDescription.

* fast/mediastream/RTCPeerConnection-setLocalDescription-offer-expected.txt: Added.
* fast/mediastream/RTCPeerConnection-setLocalDescription-offer.html: Added.
Set two local offers as local descriptions and inspect the state changes. Also set some
descriptions with bad types.
* platform/mac/TestExpectations:
Skip the above test for mac (not building with WEB_RTC)

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@201798 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/mediastream/RTCPeerConnection-setLocalDescription-offer-expected.txt [new file with mode: 0644]
LayoutTests/fast/mediastream/RTCPeerConnection-setLocalDescription-offer.html [new file with mode: 0644]
LayoutTests/platform/mac/TestExpectations
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/Modules/mediastream/MediaEndpointPeerConnection.cpp
Source/WebCore/Modules/mediastream/MediaEndpointPeerConnection.h
Source/WebCore/Modules/mediastream/MediaEndpointSessionDescription.cpp [new file with mode: 0644]
Source/WebCore/Modules/mediastream/MediaEndpointSessionDescription.h [new file with mode: 0644]
Source/WebCore/WebCore.xcodeproj/project.pbxproj

index 1f40cb1..41de3c8 100644 (file)
@@ -1,3 +1,19 @@
+2016-06-08  Adam Bergkvist  <adam.bergkvist@ericsson.com>
+
+        WebRTC: Imlement MediaEndpointPeerConnection::setLocalDescription()
+        https://bugs.webkit.org/show_bug.cgi?id=158190
+
+        Reviewed by Eric Carlson.
+
+        Add new test for RTCPeerConnection.setLocalDescription.
+
+        * fast/mediastream/RTCPeerConnection-setLocalDescription-offer-expected.txt: Added.
+        * fast/mediastream/RTCPeerConnection-setLocalDescription-offer.html: Added.
+        Set two local offers as local descriptions and inspect the state changes. Also set some
+        descriptions with bad types.
+        * platform/mac/TestExpectations:
+        Skip the above test for mac (not building with WEB_RTC)
+
 2016-06-07  Chris Dumez  <cdumez@apple.com>
 
         Expose Event / EventTarget properties on WorkerGlobalScope
diff --git a/LayoutTests/fast/mediastream/RTCPeerConnection-setLocalDescription-offer-expected.txt b/LayoutTests/fast/mediastream/RTCPeerConnection-setLocalDescription-offer-expected.txt
new file mode 100644 (file)
index 0000000..590c881
--- /dev/null
@@ -0,0 +1,58 @@
+Test RTCPeerConnection.setLocalDescription called with an RTCSessionDescription of type 'offer'
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS pc.signalingState is 'stable'
+PASS pc.localDescription is null
+PASS pc.pendingLocalDescription is null
+PASS pc.currentLocalDescription is null
+
+PASS Got stream
+*** Add audioTrack.
+PASS pc.getTransceivers().length is 1
+PASS audioTransceiver.mid is null
+
+PASS Got firstOffer
+PASS pc.signalingState is 'stable'
+PASS audioTransceiver.mid is null
+
+PASS firstOffer set as local description
+PASS pc.signalingState is 'have-local-offer'
+PASS pc.localDescription is firstOffer
+PASS pc.pendingLocalDescription is firstOffer
+PASS pc.currentLocalDescription is null
+PASS firstOffer.type is pc.localDescription.type
+PASS firstOffer.sdp is pc.localDescription.sdp
+PASS typeof pc.getTransceivers()[0].mid == 'string' is true
+Set firstOffer as local description again.
+
+PASS firstOffer set as local description (again)
+PASS pc.signalingState is 'have-local-offer'
+
+*** Try setting local descriptions with bad types for the current state
+PASS promise pc.setLocalDescription(new RTCSessionDescription({type:'answer', sdp:firstOffer.sdp})); rejected with Error: InvalidStateError: DOM Exception 11
+PASS promise pc.setLocalDescription(new RTCSessionDescription({type:'pranswer', sdp:firstOffer.sdp})); rejected with Error: InvalidStateError: DOM Exception 11
+
+*** Add videoTrack
+PASS pc.getTransceivers().length is 2
+*** Find the transceiver for the newly added video track
+PASS videoTransceiver is defined.
+PASS videoTransceiver.mid is null
+
+PASS Got secondOffer
+PASS pc.signalingState is 'have-local-offer'
+PASS videoTransceiver.mid is null
+
+PASS secondOffer set as local description
+PASS pc.signalingState is 'have-local-offer'
+PASS pc.localDescription is secondOffer
+PASS pc.pendingLocalDescription is secondOffer
+PASS pc.currentLocalDescription is null
+PASS secondOffer.type is pc.localDescription.type
+PASS secondOffer.sdp is pc.localDescription.sdp
+PASS typeof videoTransceiver.mid == 'string' is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/mediastream/RTCPeerConnection-setLocalDescription-offer.html b/LayoutTests/fast/mediastream/RTCPeerConnection-setLocalDescription-offer.html
new file mode 100644 (file)
index 0000000..6a9c69b
--- /dev/null
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+    <head>
+        <script src="../../resources/js-test-pre.js"></script>
+        <script src="resources/promise-utils.js"></script>
+    </head>
+    <body>
+        <script>
+            var stream;
+            var audioTrack;
+            var videoTrack;
+            var firstOffer;
+            var audioTransceiver;
+            var videoTransceiver;
+
+            description("Test RTCPeerConnection.setLocalDescription called with an RTCSessionDescription of type 'offer'");
+
+            if (window.testRunner)
+                testRunner.setUserMediaPermission(true);
+            else {
+                debug("This test can not be run without the testRunner");
+                finishJSTest();
+            }
+
+            var pc = new webkitRTCPeerConnection({iceServers:[{urls:'stun:foo.com'}]});
+
+            shouldBe("pc.signalingState", "'stable'");
+
+            shouldBeNull("pc.localDescription");
+            shouldBeNull("pc.pendingLocalDescription");
+            shouldBeNull("pc.currentLocalDescription");
+            debug("");
+
+            navigator.mediaDevices.getUserMedia({ "audio": true, "video": true })
+            .then(function (s) {
+                testPassed("Got stream");
+                stream = s;
+                audioTrack = stream.getAudioTracks()[0];
+                videoTrack = stream.getVideoTracks()[0];
+
+                debug("*** Add audioTrack.");
+                pc.addTrack(audioTrack, stream);
+
+                shouldBe("pc.getTransceivers().length", "1");
+                audioTransceiver = pc.getTransceivers()[0];
+                shouldBeNull("audioTransceiver.mid");
+                debug("");
+
+                return pc.createOffer();
+            })
+            .then(function (offer) {
+                testPassed("Got firstOffer");
+                firstOffer = offer;
+
+                shouldBe("pc.signalingState", "'stable'");
+                shouldBeNull("audioTransceiver.mid");
+                debug("");
+
+                return pc.setLocalDescription(firstOffer);
+            })
+            .then(function () {
+                testPassed("firstOffer set as local description");
+
+                shouldBe("pc.signalingState", "'have-local-offer'");
+
+                shouldBe("pc.localDescription", "firstOffer");
+                shouldBe("pc.pendingLocalDescription", "firstOffer");
+                shouldBeNull("pc.currentLocalDescription");
+
+                shouldBe("firstOffer.type", "pc.localDescription.type");
+                shouldBe("firstOffer.sdp", "pc.localDescription.sdp");
+
+                shouldBeTrue("typeof pc.getTransceivers()[0].mid == 'string'");
+
+                debug("Set firstOffer as local description again.");
+                debug("");
+
+                return pc.setLocalDescription(firstOffer);
+            })
+            .then(function () {
+                testPassed("firstOffer set as local description (again)");
+
+                shouldBe("pc.signalingState", "'have-local-offer'");
+                debug("");
+
+                debug("*** Try setting local descriptions with bad types for the current state");
+                return promiseShouldReject("pc.setLocalDescription(new RTCSessionDescription({type:'answer', sdp:firstOffer.sdp}));");
+            })
+            .then(function () {
+                return promiseShouldReject("pc.setLocalDescription(new RTCSessionDescription({type:'pranswer', sdp:firstOffer.sdp}));");
+            })
+            .then(function () {
+                debug("");
+
+                debug("*** Add videoTrack");
+                pc.addTrack(videoTrack, stream);
+
+                shouldBe("pc.getTransceivers().length", "2");
+
+                debug("*** Find the transceiver for the newly added video track");
+                var transceivers = pc.getTransceivers();
+                videoTransceiver = transceivers[1].sender.track.kind == "video" ? transceivers[1] : transceivers[0];
+                shouldBeDefined("videoTransceiver");
+
+                shouldBeNull("videoTransceiver.mid");
+                debug("");
+
+                return pc.createOffer();
+            })
+            .then(function (offer) {
+                testPassed("Got secondOffer");
+                secondOffer = offer;
+
+                shouldBe("pc.signalingState", "'have-local-offer'");
+                shouldBeNull("videoTransceiver.mid");
+                debug("");
+
+                return pc.setLocalDescription(secondOffer);
+            })
+            .then(function () {
+                testPassed("secondOffer set as local description");
+
+                shouldBe("pc.signalingState", "'have-local-offer'");
+
+                shouldBe("pc.localDescription", "secondOffer");
+                shouldBe("pc.pendingLocalDescription", "secondOffer");
+                shouldBeNull("pc.currentLocalDescription");
+
+                shouldBe("secondOffer.type", "pc.localDescription.type");
+                shouldBe("secondOffer.sdp", "pc.localDescription.sdp");
+
+                shouldBeTrue("typeof videoTransceiver.mid == 'string'");
+
+                finishJSTest();
+            })
+            .catch(function (error) {
+                testFailed("Error caught in promise chain: " + error);
+                finishJSTest();
+            });
+
+            window.jsTestIsAsync = true;
+            window.successfullyParsed = true;
+
+        </script>
+        <script src="../../resources/js-test-post.js"></script>
+    </body>
+</html>
index d8b414f..5827fa4 100644 (file)
@@ -190,6 +190,7 @@ fast/mediastream/RTCPeerConnection-overloaded-operations.html
 fast/mediastream/RTCPeerConnection-addTransceiver.html
 fast/mediastream/RTCRtpSender-replaceTrack.html
 fast/mediastream/RTCSessionDescription.html
+fast/mediastream/RTCPeerConnection-setLocalDescription-offer.html
 
 # Asserts in debug.
 [ Debug ] fast/images/large-size-image-crash.html [ Skip ]
index ac18818..61ab4de 100644 (file)
@@ -910,6 +910,7 @@ set(WebCore_SOURCES
     Modules/mediastream/MediaDevices.cpp
     Modules/mediastream/MediaDevicesRequest.cpp
     Modules/mediastream/MediaEndpointPeerConnection.cpp
+    Modules/mediastream/MediaEndpointSessionDescription.cpp
     Modules/mediastream/MediaSourceSettings.cpp
     Modules/mediastream/MediaStream.cpp
     Modules/mediastream/MediaStreamEvent.cpp
index 532d96e..7b2efd9 100644 (file)
@@ -1,3 +1,47 @@
+2016-06-08  Adam Bergkvist  <adam.bergkvist@ericsson.com>
+
+        WebRTC: Imlement MediaEndpointPeerConnection::setLocalDescription()
+        https://bugs.webkit.org/show_bug.cgi?id=158190
+
+        Reviewed by Eric Carlson.
+
+        Add implementation for MediaEndpointPeerConnection::setLocalDescription. This function
+        parses the input SDP, configures the media backend and updates the
+        RTCPeerConnection state.
+
+        This change adds MediaEndpointSessionDescription which is an object representation
+        of an RTCSessionDescription (which contains an SDP string).
+
+        Test: fast/mediastream/RTCPeerConnection-setLocalDescription-offer.html
+
+        * CMakeLists.txt:
+        Add MediaEndpointSessionDescription.
+        * Modules/mediastream/MediaEndpointPeerConnection.cpp:
+        (WebCore::hasUnassociatedTransceivers):
+        (WebCore::MediaEndpointPeerConnection::createOfferTask):
+        (WebCore::MediaEndpointPeerConnection::setLocalDescription):
+        (WebCore::MediaEndpointPeerConnection::setLocalDescriptionTask):
+        Add implementation.
+        (WebCore::MediaEndpointPeerConnection::localDescription):
+        (WebCore::MediaEndpointPeerConnection::currentLocalDescription):
+        (WebCore::MediaEndpointPeerConnection::pendingLocalDescription):
+        (WebCore::MediaEndpointPeerConnection::localDescriptionTypeValidForState):
+        (WebCore::MediaEndpointPeerConnection::internalLocalDescription):
+        (WebCore::MediaEndpointPeerConnection::createRTCSessionDescription):
+        * Modules/mediastream/MediaEndpointPeerConnection.h:
+        * Modules/mediastream/MediaEndpointSessionDescription.cpp: Added.
+        (WebCore::MediaEndpointSessionDescription::create):
+        (WebCore::MediaEndpointSessionDescription::toRTCSessionDescription):
+        (WebCore::MediaEndpointSessionDescription::typeString):
+        (WebCore::MediaEndpointSessionDescription::isLaterThan):
+        * Modules/mediastream/MediaEndpointSessionDescription.h: Added.
+        (WebCore::MediaEndpointSessionDescription::~MediaEndpointSessionDescription):
+        (WebCore::MediaEndpointSessionDescription::type):
+        (WebCore::MediaEndpointSessionDescription::configuration):
+        (WebCore::MediaEndpointSessionDescription::MediaEndpointSessionDescription):
+        * WebCore.xcodeproj/project.pbxproj:
+        Add MediaEndpointSessionDescription.
+
 2016-06-07  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [GLIB] Implement hardLinkOrCopyFile() in FileSystemGlib
index 79dcbf2..733924a 100644 (file)
 #if ENABLE(WEB_RTC)
 #include "MediaEndpointPeerConnection.h"
 
+#include "Event.h"
 #include "JSRTCSessionDescription.h"
 #include "MediaEndpointSessionConfiguration.h"
 #include "MediaStreamTrack.h"
+#include "PeerMediaDescription.h"
 #include "RTCOfferAnswerOptions.h"
 #include "RTCRtpTransceiver.h"
 #include "SDPProcessor.h"
@@ -95,6 +97,13 @@ static RTCRtpTransceiver* matchTransceiverByMid(const RtpTransceiverVector& tran
     });
 }
 
+static bool hasUnassociatedTransceivers(const RtpTransceiverVector& transceivers)
+{
+    return matchTransceiver(transceivers, [] (RTCRtpTransceiver& current) {
+        return current.mid().isNull() && !current.stopped();
+    });
+}
+
 void MediaEndpointPeerConnection::runTask(NoncopyableFunction<void ()>&& task)
 {
     if (m_dtlsFingerprint.isNull()) {
@@ -128,7 +137,9 @@ void MediaEndpointPeerConnection::createOfferTask(RTCOfferOptions&, SessionDescr
     if (m_client->internalSignalingState() == SignalingState::Closed)
         return;
 
-    RefPtr<MediaEndpointSessionConfiguration> configurationSnapshot = MediaEndpointSessionConfiguration::create();
+    MediaEndpointSessionDescription* localDescription = internalLocalDescription();
+    RefPtr<MediaEndpointSessionConfiguration> configurationSnapshot = localDescription ?
+        localDescription->configuration()->clone() : MediaEndpointSessionConfiguration::create();
 
     configurationSnapshot->setSessionVersion(m_sdpOfferSessionVersion++);
 
@@ -200,32 +211,121 @@ void MediaEndpointPeerConnection::createAnswer(RTCAnswerOptions& options, Sessio
 
 void MediaEndpointPeerConnection::setLocalDescription(RTCSessionDescription& description, VoidPromise&& promise)
 {
-    UNUSED_PARAM(description);
+    runTask([this, protectedDescription = RefPtr<RTCSessionDescription>(&description), protectedPromise = WTFMove(promise)]() mutable {
+        setLocalDescriptionTask(WTFMove(protectedDescription), protectedPromise);
+    });
+}
 
-    notImplemented();
+void MediaEndpointPeerConnection::setLocalDescriptionTask(RefPtr<RTCSessionDescription>&& description, VoidPromise& promise)
+{
+    if (m_client->internalSignalingState() == SignalingState::Closed)
+        return;
 
-    promise.reject(NOT_SUPPORTED_ERR);
+    ExceptionCodeWithMessage exception;
+    auto newDescription = MediaEndpointSessionDescription::create(WTFMove(description), *m_sdpProcessor, exception);
+    if (exception.code) {
+        promise.reject(exception.code, exception.message);
+        return;
+    }
+
+    if (!localDescriptionTypeValidForState(newDescription->type())) {
+        promise.reject(INVALID_STATE_ERR, "Description type incompatible with current signaling state");
+        return;
+    }
+
+    const RtpTransceiverVector& transceivers = m_client->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() == RTCSessionDescription::SdpType::Offer;
+
+    if (hasNewMediaDescriptions) {
+        MediaEndpoint::UpdateResult result = m_mediaEndpoint->updateReceiveConfiguration(newDescription->configuration(), isInitiator);
+
+        if (result == MediaEndpoint::UpdateResult::SuccessWithIceRestart) {
+            if (m_client->internalIceGatheringState() != IceGatheringState::Gathering)
+                m_client->updateIceGatheringState(IceGatheringState::Gathering);
+
+            if (m_client->internalIceConnectionState() != IceConnectionState::Completed)
+                m_client->updateIceConnectionState(IceConnectionState::Connected);
+
+            LOG_ERROR("ICE restart is not implemented");
+            notImplemented();
+
+        } else if (result == MediaEndpoint::UpdateResult::Failed) {
+            promise.reject(OperationError, "Unable to apply session description");
+            return;
+        }
+
+        // Associate media descriptions with transceivers (set provisional mid to 'final' mid).
+        for (unsigned i = previousNumberOfMediaDescriptions; i < mediaDescriptions.size(); ++i) {
+            PeerMediaDescription& mediaDescription = *mediaDescriptions[i];
+
+            RTCRtpTransceiver* transceiver = matchTransceiver(transceivers, [&mediaDescription] (RTCRtpTransceiver& current) {
+                return current.provisionalMid() == mediaDescription.mid();
+            });
+            if (transceiver)
+                transceiver->setMid(transceiver->provisionalMid());
+        }
+    }
+
+    if (!hasUnassociatedTransceivers(transceivers))
+        clearNegotiationNeededState();
+
+    SignalingState newSignalingState;
+
+    // Update state and local descriptions according to setLocal/RemoteDescription processing model
+    switch (newDescription->type()) {
+    case RTCSessionDescription::SdpType::Offer:
+        m_pendingLocalDescription = newDescription;
+        newSignalingState = SignalingState::HaveLocalOffer;
+        break;
+
+    case RTCSessionDescription::SdpType::Answer:
+        m_currentLocalDescription = newDescription;
+        m_pendingLocalDescription = nullptr;
+        newSignalingState = SignalingState::Stable;
+        break;
+
+    case RTCSessionDescription::SdpType::Rollback:
+        m_pendingLocalDescription = nullptr;
+        newSignalingState = SignalingState::Stable;
+        break;
+
+    case RTCSessionDescription::SdpType::Pranswer:
+        m_pendingLocalDescription = newDescription;
+        newSignalingState = SignalingState::HaveLocalPrAnswer;
+        break;
+    }
+
+    if (newSignalingState != m_client->internalSignalingState()) {
+        m_client->setSignalingState(newSignalingState);
+        m_client->fireEvent(Event::create(eventNames().signalingstatechangeEvent, false, false));
+    }
+
+    if (m_client->internalIceGatheringState() == IceGatheringState::New && mediaDescriptions.size())
+        m_client->updateIceGatheringState(IceGatheringState::Gathering);
+
+    if (m_client->internalSignalingState() == SignalingState::Stable && m_negotiationNeeded)
+        m_client->scheduleNegotiationNeededEvent();
+
+    promise.resolve(nullptr);
 }
 
 RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::localDescription() const
 {
-    notImplemented();
-
-    return nullptr;
+    return createRTCSessionDescription(internalLocalDescription());
 }
 
 RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::currentLocalDescription() const
 {
-    notImplemented();
-
-    return nullptr;
+    return createRTCSessionDescription(m_currentLocalDescription.get());
 }
 
 RefPtr<RTCSessionDescription> MediaEndpointPeerConnection::pendingLocalDescription() const
 {
-    notImplemented();
-
-    return nullptr;
+    return createRTCSessionDescription(m_pendingLocalDescription.get());
 }
 
 void MediaEndpointPeerConnection::setRemoteDescription(RTCSessionDescription& description, VoidPromise&& promise)
@@ -314,6 +414,35 @@ void MediaEndpointPeerConnection::markAsNeedingNegotiation()
     notImplemented();
 }
 
+bool MediaEndpointPeerConnection::localDescriptionTypeValidForState(RTCSessionDescription::SdpType type) const
+{
+    switch (m_client->internalSignalingState()) {
+    case SignalingState::Stable:
+        return type == RTCSessionDescription::SdpType::Offer;
+    case SignalingState::HaveLocalOffer:
+        return type == RTCSessionDescription::SdpType::Offer;
+    case SignalingState::HaveRemoteOffer:
+        return type == RTCSessionDescription::SdpType::Answer || type == RTCSessionDescription::SdpType::Pranswer;
+    case SignalingState::HaveLocalPrAnswer:
+        return type == RTCSessionDescription::SdpType::Answer || type == RTCSessionDescription::SdpType::Pranswer;
+    default:
+        return false;
+    };
+
+    ASSERT_NOT_REACHED();
+    return false;
+}
+
+MediaEndpointSessionDescription* MediaEndpointPeerConnection::internalLocalDescription() const
+{
+    return m_pendingLocalDescription ? m_pendingLocalDescription.get() : m_currentLocalDescription.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());
index f69a23e..ecb2929 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Ericsson AB. All rights reserved.
+ * Copyright (C) 2015, 2016 Ericsson AB. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #if ENABLE(WEB_RTC)
 
 #include "MediaEndpoint.h"
+#include "MediaEndpointSessionDescription.h"
 #include "NotImplemented.h"
 #include "PeerConnectionBackend.h"
-#include "RTCSessionDescription.h"
 #include <wtf/NoncopyableFunction.h>
 #include <wtf/RefPtr.h>
 
 namespace WebCore {
 
 class MediaStreamTrack;
+class PeerMediaDescription;
 class SDPProcessor;
 
+typedef Vector<RefPtr<PeerMediaDescription>> MediaDescriptionVector;
 typedef Vector<RefPtr<RTCRtpSender>> RtpSenderVector;
 typedef Vector<RefPtr<RTCRtpTransceiver>> RtpTransceiverVector;
 
@@ -85,6 +87,13 @@ private:
 
     void createOfferTask(RTCOfferOptions&, PeerConnection::SessionDescriptionPromise&);
 
+    void setLocalDescriptionTask(RefPtr<RTCSessionDescription>&&, PeerConnection::VoidPromise&);
+
+    bool localDescriptionTypeValidForState(RTCSessionDescription::SdpType) const;
+
+    MediaEndpointSessionDescription* internalLocalDescription() const;
+    RefPtr<RTCSessionDescription> createRTCSessionDescription(MediaEndpointSessionDescription*) const;
+
     // MediaEndpointClient
     void gotDtlsFingerprint(const String& fingerprint, const String& fingerprintFunction) override;
     void gotIceCandidate(unsigned mdescIndex, RefPtr<IceCandidate>&&) override;
@@ -107,6 +116,11 @@ private:
     String m_dtlsFingerprint;
     String m_dtlsFingerprintFunction;
     unsigned m_sdpOfferSessionVersion { 0 };
+
+    RefPtr<MediaEndpointSessionDescription> m_currentLocalDescription;
+    RefPtr<MediaEndpointSessionDescription> m_pendingLocalDescription;
+
+    bool m_negotiationNeeded { false };
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/Modules/mediastream/MediaEndpointSessionDescription.cpp b/Source/WebCore/Modules/mediastream/MediaEndpointSessionDescription.cpp
new file mode 100644 (file)
index 0000000..7095d7e
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015, 2016 Ericsson AB. 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 "MediaEndpointSessionDescription.h"
+
+#if ENABLE(WEB_RTC)
+
+#include "SDPProcessor.h"
+#include <wtf/NeverDestroyed.h>
+
+namespace WebCore {
+
+#define STRING_FUNCTION(name) \
+    static const String& name##String() \
+    { \
+        static NeverDestroyed<const String> name { ASCIILiteral(#name) }; \
+        return name; \
+    }
+
+STRING_FUNCTION(offer)
+STRING_FUNCTION(pranswer)
+STRING_FUNCTION(answer)
+STRING_FUNCTION(rollback)
+
+Ref<MediaEndpointSessionDescription> MediaEndpointSessionDescription::create(RTCSessionDescription::SdpType type, RefPtr<MediaEndpointSessionConfiguration>&& configuration)
+{
+    return adoptRef(*new MediaEndpointSessionDescription(type, WTFMove(configuration), nullptr));
+}
+
+RefPtr<MediaEndpointSessionDescription> MediaEndpointSessionDescription::create(RefPtr<RTCSessionDescription>&& rtcDescription, const SDPProcessor& sdpProcessor, ExceptionCodeWithMessage& exception)
+{
+    RefPtr<MediaEndpointSessionConfiguration> configuration;
+    SDPProcessor::Result result = sdpProcessor.parse(rtcDescription->sdp(), configuration);
+    if (result != SDPProcessor::Result::Success) {
+        if (result == SDPProcessor::Result::ParseError) {
+            exception.code = INVALID_ACCESS_ERR;
+            exception.message = ASCIILiteral("SDP content is invalid");
+            return nullptr;
+        }
+        LOG_ERROR("SDPProcessor internal error");
+        exception.code = ABORT_ERR;
+        exception.message = ASCIILiteral("Internal error");
+        return nullptr;
+    }
+
+    return adoptRef(new MediaEndpointSessionDescription(rtcDescription->type(), WTFMove(configuration), WTFMove(rtcDescription)));
+}
+
+RefPtr<RTCSessionDescription> MediaEndpointSessionDescription::toRTCSessionDescription(const SDPProcessor& sdpProcessor) const
+{
+    String sdpString;
+    SDPProcessor::Result result = sdpProcessor.generate(*m_configuration, sdpString);
+    if (result != SDPProcessor::Result::Success) {
+        LOG_ERROR("SDPProcessor internal error");
+        return nullptr;
+    }
+
+    // If this object was created from an RTCSessionDescription, toRTCSessionDescription will return
+    // that same instance but with an updated sdp. It is used for RTCPeerConnection's description
+    // atributes (e.g. localDescription and pendingLocalDescription).
+    if (m_rtcDescription) {
+        m_rtcDescription->setSdp(sdpString);
+        return m_rtcDescription;
+    }
+
+    return RTCSessionDescription::create(m_type, sdpString);
+}
+
+const String& MediaEndpointSessionDescription::typeString() const
+{
+    switch (m_type) {
+    case RTCSessionDescription::SdpType::Offer:
+        return offerString();
+    case RTCSessionDescription::SdpType::Pranswer:
+        return pranswerString();
+    case RTCSessionDescription::SdpType::Answer:
+        return answerString();
+    case RTCSessionDescription::SdpType::Rollback:
+        return rollbackString();
+    }
+
+    ASSERT_NOT_REACHED();
+    return emptyString();
+}
+
+bool MediaEndpointSessionDescription::isLaterThan(MediaEndpointSessionDescription* other) const
+{
+    return !other || configuration()->sessionVersion() > other->configuration()->sessionVersion();
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_RTC)
diff --git a/Source/WebCore/Modules/mediastream/MediaEndpointSessionDescription.h b/Source/WebCore/Modules/mediastream/MediaEndpointSessionDescription.h
new file mode 100644 (file)
index 0000000..ecaa2bb
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015, 2016 Ericsson AB. 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.
+ */
+
+#ifndef MediaEndpointSessionDescription_h
+#define MediaEndpointSessionDescription_h
+
+#if ENABLE(WEB_RTC)
+
+#include "RTCSessionDescription.h"
+#include <wtf/RefCounted.h>
+#include <wtf/RefPtr.h>
+
+namespace WebCore {
+
+class MediaEndpointSessionConfiguration;
+class SDPProcessor;
+class DOMError;
+
+class MediaEndpointSessionDescription : public RefCounted<MediaEndpointSessionDescription> {
+public:
+    static Ref<MediaEndpointSessionDescription> create(RTCSessionDescription::SdpType, RefPtr<MediaEndpointSessionConfiguration>&&);
+    static RefPtr<MediaEndpointSessionDescription> create(RefPtr<RTCSessionDescription>&&, const SDPProcessor&, ExceptionCodeWithMessage&);
+    virtual ~MediaEndpointSessionDescription() { }
+
+    RefPtr<RTCSessionDescription> toRTCSessionDescription(const SDPProcessor&) const;
+
+    RTCSessionDescription::SdpType type() const { return m_type; }
+    const String& typeString() const;
+    MediaEndpointSessionConfiguration* configuration() const { return m_configuration.get(); }
+
+    bool isLaterThan(MediaEndpointSessionDescription* other) const;
+
+private:
+    MediaEndpointSessionDescription(RTCSessionDescription::SdpType type, RefPtr<MediaEndpointSessionConfiguration>&& configuration, RefPtr<RTCSessionDescription>&& rtcDescription)
+        : m_type(type)
+        , m_configuration(configuration)
+        , m_rtcDescription(WTFMove(rtcDescription))
+    { }
+
+    RTCSessionDescription::SdpType m_type;
+    RefPtr<MediaEndpointSessionConfiguration> m_configuration;
+
+    RefPtr<RTCSessionDescription> m_rtcDescription;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_RTC)
+
+#endif // MediaEndpointSessionDescription_h
index e3d262e..f2f6370 100644 (file)
                5E2C43741BCF0D750001E2BC /* JSRTCRtpSender.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E2C43701BCF0D690001E2BC /* JSRTCRtpSender.h */; };
                5E2C437B1BCF9A570001E2BC /* RTCPeerConnectionBuiltins.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E2C43761BCF9A0B0001E2BC /* RTCPeerConnectionBuiltins.h */; settings = {ATTRIBUTES = (Private, ); }; };
                5E2C437C1BCF9A840001E2BC /* RTCPeerConnectionInternalsBuiltins.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E2C43791BCF9A0B0001E2BC /* RTCPeerConnectionInternalsBuiltins.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               5E4EAB041D07166A0006A184 /* MediaEndpointSessionDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E4EAB031D07164C0006A184 /* MediaEndpointSessionDescription.h */; };
+               5E4EAB051D07166E0006A184 /* MediaEndpointSessionDescription.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E4EAB021D07164C0006A184 /* MediaEndpointSessionDescription.cpp */; };
                5E5E2B131CFC3E70000C0D85 /* RTCRtpTransceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E5E2B101CFC3E4B000C0D85 /* RTCRtpTransceiver.cpp */; };
                5E5E2B141CFC3E75000C0D85 /* RTCRtpTransceiver.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E5E2B111CFC3E4B000C0D85 /* RTCRtpTransceiver.h */; settings = {ATTRIBUTES = (Private, ); }; };
                5EA3D6DF1C859D7F00300BBB /* MockMediaEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 5EA3D6DE1C859D5300300BBB /* MockMediaEndpoint.h */; };
                5E2C43761BCF9A0B0001E2BC /* RTCPeerConnectionBuiltins.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCPeerConnectionBuiltins.h; sourceTree = "<group>"; };
                5E2C43781BCF9A0B0001E2BC /* RTCPeerConnectionInternalsBuiltins.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RTCPeerConnectionInternalsBuiltins.cpp; sourceTree = "<group>"; };
                5E2C43791BCF9A0B0001E2BC /* RTCPeerConnectionInternalsBuiltins.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCPeerConnectionInternalsBuiltins.h; sourceTree = "<group>"; };
+               5E4EAB021D07164C0006A184 /* MediaEndpointSessionDescription.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MediaEndpointSessionDescription.cpp; sourceTree = "<group>"; };
+               5E4EAB031D07164C0006A184 /* MediaEndpointSessionDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaEndpointSessionDescription.h; sourceTree = "<group>"; };
                5E5E2B101CFC3E4B000C0D85 /* RTCRtpTransceiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RTCRtpTransceiver.cpp; sourceTree = "<group>"; };
                5E5E2B111CFC3E4B000C0D85 /* RTCRtpTransceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCRtpTransceiver.h; sourceTree = "<group>"; };
                5E5E2B121CFC3E4B000C0D85 /* RTCRtpTransceiver.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = RTCRtpTransceiver.idl; sourceTree = "<group>"; };
                                07394EC91BAB2CD700BE99CD /* MediaDevicesRequest.h */,
                                5E16A2E21BFA64FB0029A21E /* MediaEndpointPeerConnection.cpp */,
                                5E16A2E31BFA64FB0029A21E /* MediaEndpointPeerConnection.h */,
+                               5E4EAB021D07164C0006A184 /* MediaEndpointSessionDescription.cpp */,
+                               5E4EAB031D07164C0006A184 /* MediaEndpointSessionDescription.h */,
                                07C59B6517F784BA000FBCBB /* MediaSourceSettings.cpp */,
                                07C59B6617F784BA000FBCBB /* MediaSourceSettings.h */,
                                07221B4C17CEC32700848E51 /* MediaStream.cpp */,
                                E1C36D350EB0A094007410BC /* JSWorkerGlobalScopeBase.h in Headers */,
                                E1C362EF0EAF2AA9007410BC /* JSWorkerLocation.h in Headers */,
                                E1271A580EEECDE400F61213 /* JSWorkerNavigator.h in Headers */,
+                               5E4EAB041D07166A0006A184 /* MediaEndpointSessionDescription.h in Headers */,
                                7C4C96DD1AD4483500365A60 /* JSWritableStream.h in Headers */,
                                8358CB701C53277500E0C2D8 /* JSXMLDocument.h in Headers */,
                                BC348BD40DB7F804004ABAB9 /* JSXMLHttpRequest.h in Headers */,
                                E4D58EB817B4ED8900CBDCA8 /* StyleFontSizeFunctions.cpp in Sources */,
                                BCEF447D0E674806001C1287 /* StyleGeneratedImage.cpp in Sources */,
                                A10DC76A14747BAB005E2471 /* StyleGridData.cpp in Sources */,
+                               5E4EAB051D07166E0006A184 /* MediaEndpointSessionDescription.cpp in Sources */,
                                A110DB9D14F5DF8700A03B93 /* StyleGridItemData.cpp in Sources */,
                                BC2273030E82F1E600E7F975 /* StyleInheritedData.cpp in Sources */,
                                E47127CA163438A100ED6F5A /* StyleInvalidationAnalysis.cpp in Sources */,