[PeerConnection] Implement parameterless SetLocalDescription().

For background, motivation, requirements and implementation notes, see
https://docs.google.com/document/d/1XLwNN2kUIGGTwz9LQ0NwJNkcybi9oKnynUEZB1jGA14/edit?usp=sharing

The parameterless SetLocalDescription() will implicitly create an
offer or answer to be set by chaining create offer or answer with
setting the session description, as per spec:
https://w3c.github.io/webrtc-pc/#dom-peerconnection-setlocaldescription

Bug: chromium:980885
Change-Id: Ia430160869df18fd47b756b9adf9e7e23ba8e969
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/157444
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29653}
This commit is contained in:
Henrik Boström
2019-10-30 10:35:50 +01:00
committed by Commit Bot
parent 9b66114878
commit 4e19670d3a
6 changed files with 316 additions and 10 deletions

View File

@ -991,6 +991,11 @@ class RTC_EXPORT PeerConnectionInterface : public rtc::RefCountInterface {
// that this method always takes ownership of it. // that this method always takes ownership of it.
virtual void SetLocalDescription(SetSessionDescriptionObserver* observer, virtual void SetLocalDescription(SetSessionDescriptionObserver* observer,
SessionDescriptionInterface* desc) = 0; SessionDescriptionInterface* desc) = 0;
// Implicitly creates an offer or answer (depending on the current signaling
// state) and performs SetLocalDescription() with the newly generated session
// description.
// TODO(hbos): Make pure virtual when implemented by downstream projects.
virtual void SetLocalDescription(SetSessionDescriptionObserver* observer) {}
// Sets the remote session description. // Sets the remote session description.
// The PeerConnection takes the ownership of |desc| even if it fails. // The PeerConnection takes the ownership of |desc| even if it fails.
// The |observer| callback will be called when done. // The |observer| callback will be called when done.

View File

@ -100,6 +100,7 @@ PROXY_METHOD2(void,
SetLocalDescription, SetLocalDescription,
SetSessionDescriptionObserver*, SetSessionDescriptionObserver*,
SessionDescriptionInterface*) SessionDescriptionInterface*)
PROXY_METHOD1(void, SetLocalDescription, SetSessionDescriptionObserver*)
PROXY_METHOD2(void, PROXY_METHOD2(void,
SetRemoteDescription, SetRemoteDescription,
SetSessionDescriptionObserver*, SetSessionDescriptionObserver*,

View File

@ -694,6 +694,85 @@ class CreateSessionDescriptionObserverOperationWrapper
} // namespace } // namespace
// Used by parameterless SetLocalDescription() to create an offer or answer.
// Upon completion of creating the session description, SetLocalDescription() is
// invoked with the result.
// For consistency with DoSetLocalDescription(), if the PeerConnection is
// destroyed midst operation, we DO NOT inform the
// |set_local_description_observer| that the operation failed.
// TODO(hbos): If/when we process SLD messages in ~PeerConnection, the
// consistent thing would be to inform the observer here.
class PeerConnection::ImplicitCreateSessionDescriptionObserver
: public CreateSessionDescriptionObserver {
public:
ImplicitCreateSessionDescriptionObserver(
rtc::WeakPtr<PeerConnection> pc,
rtc::scoped_refptr<SetSessionDescriptionObserver>
set_local_description_observer)
: pc_(std::move(pc)),
set_local_description_observer_(
std::move(set_local_description_observer)) {}
~ImplicitCreateSessionDescriptionObserver() override {
RTC_DCHECK(was_called_);
}
void SetOperationCompleteCallback(
std::function<void()> operation_complete_callback) {
operation_complete_callback_ = std::move(operation_complete_callback);
}
bool was_called() const { return was_called_; }
void OnSuccess(SessionDescriptionInterface* desc_ptr) override {
RTC_DCHECK(!was_called_);
std::unique_ptr<SessionDescriptionInterface> desc(desc_ptr);
was_called_ = true;
// Abort early if |pc_| is no longer valid.
if (!pc_) {
operation_complete_callback_();
return;
}
// DoSetLocalDescription() is currently implemented as a synchronous
// operation but where the |set_local_description_observer_|'s callbacks are
// invoked asynchronously in a post to PeerConnection::OnMessage().
pc_->DoSetLocalDescription(std::move(desc),
std::move(set_local_description_observer_));
// For backwards-compatability reasons, we declare the operation as
// completed here (rather than in PeerConnection::OnMessage()). This ensures
// that subsequent offer/answer operations can start immediately (without
// waiting for OnMessage()).
operation_complete_callback_();
}
void OnFailure(RTCError error) override {
RTC_DCHECK(!was_called_);
was_called_ = true;
// Abort early if |pc_| is no longer valid.
if (!pc_) {
operation_complete_callback_();
return;
}
// DoSetLocalDescription() reports its failures in a post. We do the
// same thing here for consistency.
pc_->PostSetSessionDescriptionFailure(
set_local_description_observer_,
RTCError(error.type(),
std::string("SetLocalDescription failed to create "
"session description - ") +
error.message()));
operation_complete_callback_();
}
private:
bool was_called_ = false;
rtc::WeakPtr<PeerConnection> pc_;
rtc::scoped_refptr<SetSessionDescriptionObserver>
set_local_description_observer_;
std::function<void()> operation_complete_callback_;
};
class PeerConnection::LocalIceCredentialsToReplace { class PeerConnection::LocalIceCredentialsToReplace {
public: public:
// Sets the ICE credentials that need restarting to the ICE credentials of // Sets the ICE credentials that need restarting to the ICE credentials of
@ -2382,15 +2461,68 @@ void PeerConnection::SetLocalDescription(
// operation but where the |observer|'s callbacks are invoked // operation but where the |observer|'s callbacks are invoked
// asynchronously in a post to OnMessage(). // asynchronously in a post to OnMessage().
// For backwards-compatability reasons, we declare the operation as // For backwards-compatability reasons, we declare the operation as
// completed here (rather than in OnMessage()). This ensures that: // completed here (rather than in OnMessage()). This ensures that
// - This operation is not keeping the PeerConnection alive past this // subsequent offer/answer operations can start immediately (without
// point. // waiting for OnMessage()).
// - Subsequent offer/answer operations can start immediately (without
// waiting for OnMessage()).
operations_chain_callback(); operations_chain_callback();
}); });
} }
void PeerConnection::SetLocalDescription(
SetSessionDescriptionObserver* observer) {
RTC_DCHECK_RUN_ON(signaling_thread());
// The |create_sdp_observer| handles performing DoSetLocalDescription() with
// the resulting description as well as completing the operation.
rtc::scoped_refptr<ImplicitCreateSessionDescriptionObserver>
create_sdp_observer(
new rtc::RefCountedObject<ImplicitCreateSessionDescriptionObserver>(
weak_ptr_factory_.GetWeakPtr(),
rtc::scoped_refptr<SetSessionDescriptionObserver>(observer)));
// Chain this operation. If asynchronous operations are pending on the chain,
// this operation will be queued to be invoked, otherwise the contents of the
// lambda will execute immediately.
operations_chain_->ChainOperation(
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
create_sdp_observer](std::function<void()> operations_chain_callback) {
// The |create_sdp_observer| is responsible for completing the
// operation.
create_sdp_observer->SetOperationCompleteCallback(
std::move(operations_chain_callback));
// Abort early if |this_weak_ptr| is no longer valid. This triggers the
// same code path as if DoCreateOffer() or DoCreateAnswer() failed.
if (!this_weak_ptr) {
create_sdp_observer->OnFailure(RTCError(
RTCErrorType::INTERNAL_ERROR,
"SetLocalDescription failed because the session was shut down"));
return;
}
switch (this_weak_ptr->signaling_state()) {
case PeerConnectionInterface::kStable:
case PeerConnectionInterface::kHaveLocalOffer:
case PeerConnectionInterface::kHaveRemotePrAnswer:
// TODO(hbos): If [LastCreatedOffer] exists and still represents the
// current state of the system, use that instead of creating another
// offer.
this_weak_ptr->DoCreateOffer(RTCOfferAnswerOptions(),
create_sdp_observer);
break;
case PeerConnectionInterface::kHaveLocalPrAnswer:
case PeerConnectionInterface::kHaveRemoteOffer:
// TODO(hbos): If [LastCreatedAnswer] exists and still represents
// the current state of the system, use that instead of creating
// another answer.
this_weak_ptr->DoCreateAnswer(RTCOfferAnswerOptions(),
create_sdp_observer);
break;
case PeerConnectionInterface::kClosed:
create_sdp_observer->OnFailure(RTCError(
RTCErrorType::INVALID_STATE,
"SetLocalDescription called when PeerConnection is closed."));
break;
}
});
}
void PeerConnection::DoSetLocalDescription( void PeerConnection::DoSetLocalDescription(
std::unique_ptr<SessionDescriptionInterface> desc, std::unique_ptr<SessionDescriptionInterface> desc,
rtc::scoped_refptr<SetSessionDescriptionObserver> observer) { rtc::scoped_refptr<SetSessionDescriptionObserver> observer) {
@ -2807,11 +2939,9 @@ void PeerConnection::SetRemoteDescription(
// the |observer|'s callbacks are invoked asynchronously in a post to // the |observer|'s callbacks are invoked asynchronously in a post to
// OnMessage(). // OnMessage().
// For backwards-compatability reasons, we declare the operation as // For backwards-compatability reasons, we declare the operation as
// completed here (rather than in OnMessage()). This ensures that: // completed here (rather than in OnMessage()). This ensures that
// - This operation is not keeping the PeerConnection alive past this // subsequent offer/answer operations can start immediately (without
// point. // waiting for OnMessage()).
// - Subsequent offer/answer operations can start immediately (without
// waiting for OnMessage()).
operations_chain_callback(); operations_chain_callback();
}); });
} }

View File

@ -210,6 +210,7 @@ class PeerConnection : public PeerConnectionInternal,
const RTCOfferAnswerOptions& options) override; const RTCOfferAnswerOptions& options) override;
void SetLocalDescription(SetSessionDescriptionObserver* observer, void SetLocalDescription(SetSessionDescriptionObserver* observer,
SessionDescriptionInterface* desc) override; SessionDescriptionInterface* desc) override;
void SetLocalDescription(SetSessionDescriptionObserver* observer) override;
void SetRemoteDescription(SetSessionDescriptionObserver* observer, void SetRemoteDescription(SetSessionDescriptionObserver* observer,
SessionDescriptionInterface* desc) override; SessionDescriptionInterface* desc) override;
void SetRemoteDescription( void SetRemoteDescription(
@ -314,6 +315,8 @@ class PeerConnection : public PeerConnectionInternal,
~PeerConnection() override; ~PeerConnection() override;
private: private:
class ImplicitCreateSessionDescriptionObserver;
friend class ImplicitCreateSessionDescriptionObserver;
class SetRemoteDescriptionObserverAdapter; class SetRemoteDescriptionObserverAdapter;
friend class SetRemoteDescriptionObserverAdapter; friend class SetRemoteDescriptionObserverAdapter;
// Represents the [[LocalIceCredentialsToReplace]] internal slot in the spec. // Represents the [[LocalIceCredentialsToReplace]] internal slot in the spec.

View File

@ -565,6 +565,32 @@ TEST_P(PeerConnectionSignalingTest, CloseCreateOfferAndShutdown) {
EXPECT_TRUE(observer->called()); EXPECT_TRUE(observer->called());
} }
TEST_P(PeerConnectionSignalingTest, ImplicitCreateOfferAndShutdown) {
auto caller = CreatePeerConnection();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(observer);
caller.reset(nullptr);
EXPECT_FALSE(observer->called());
}
TEST_P(PeerConnectionSignalingTest, CloseBeforeImplicitCreateOfferAndShutdown) {
auto caller = CreatePeerConnection();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->Close();
caller->pc()->SetLocalDescription(observer);
caller.reset(nullptr);
EXPECT_FALSE(observer->called());
}
TEST_P(PeerConnectionSignalingTest, CloseAfterImplicitCreateOfferAndShutdown) {
auto caller = CreatePeerConnection();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(observer);
caller->pc()->Close();
caller.reset(nullptr);
EXPECT_FALSE(observer->called());
}
TEST_P(PeerConnectionSignalingTest, SetRemoteDescriptionExecutesImmediately) { TEST_P(PeerConnectionSignalingTest, SetRemoteDescriptionExecutesImmediately) {
auto caller = CreatePeerConnectionWithAudioVideo(); auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection(); auto callee = CreatePeerConnection();
@ -608,6 +634,143 @@ TEST_P(PeerConnectionSignalingTest, CreateOfferBlocksSetRemoteDescription) {
EXPECT_EQ(2u, callee->pc()->GetReceivers().size()); EXPECT_EQ(2u, callee->pc()->GetReceivers().size());
} }
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionCreatesOffer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(observer);
// The offer is created asynchronously; message processing is needed for it to
// complete.
EXPECT_FALSE(observer->called());
EXPECT_FALSE(caller->pc()->pending_local_description());
EXPECT_EQ(PeerConnection::kStable, caller->signaling_state());
// Wait for messages to be processed.
EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout);
EXPECT_TRUE(observer->result());
EXPECT_TRUE(caller->pc()->pending_local_description());
EXPECT_EQ(SdpType::kOffer,
caller->pc()->pending_local_description()->GetType());
EXPECT_EQ(PeerConnection::kHaveLocalOffer, caller->signaling_state());
}
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionCreatesAnswer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
callee->SetRemoteDescription(caller->CreateOffer());
EXPECT_EQ(PeerConnection::kHaveRemoteOffer, callee->signaling_state());
auto observer = MockSetSessionDescriptionObserver::Create();
callee->pc()->SetLocalDescription(observer);
// The answer is created asynchronously; message processing is needed for it
// to complete.
EXPECT_FALSE(observer->called());
EXPECT_FALSE(callee->pc()->current_local_description());
// Wait for messages to be processed.
EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout);
EXPECT_TRUE(observer->result());
EXPECT_TRUE(callee->pc()->current_local_description());
EXPECT_EQ(SdpType::kAnswer,
callee->pc()->current_local_description()->GetType());
EXPECT_EQ(PeerConnection::kStable, callee->signaling_state());
}
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionFullExchange) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
// SetLocalDescription(), implicitly creating an offer.
rtc::scoped_refptr<MockSetSessionDescriptionObserver>
caller_set_local_description_observer(
new rtc::RefCountedObject<MockSetSessionDescriptionObserver>());
caller->pc()->SetLocalDescription(caller_set_local_description_observer);
EXPECT_TRUE_WAIT(caller_set_local_description_observer->called(),
kWaitTimeout);
ASSERT_TRUE(caller->pc()->pending_local_description());
// SetRemoteDescription(offer)
rtc::scoped_refptr<MockSetSessionDescriptionObserver>
callee_set_remote_description_observer(
new rtc::RefCountedObject<MockSetSessionDescriptionObserver>());
callee->pc()->SetRemoteDescription(
callee_set_remote_description_observer.get(),
CloneSessionDescription(caller->pc()->pending_local_description())
.release());
// SetLocalDescription(), implicitly creating an answer.
rtc::scoped_refptr<MockSetSessionDescriptionObserver>
callee_set_local_description_observer(
new rtc::RefCountedObject<MockSetSessionDescriptionObserver>());
callee->pc()->SetLocalDescription(callee_set_local_description_observer);
EXPECT_TRUE_WAIT(callee_set_local_description_observer->called(),
kWaitTimeout);
// Chaining guarantees SetRemoteDescription() happened before
// SetLocalDescription().
EXPECT_TRUE(callee_set_remote_description_observer->called());
EXPECT_TRUE(callee->pc()->current_local_description());
// SetRemoteDescription(answer)
rtc::scoped_refptr<MockSetSessionDescriptionObserver>
caller_set_remote_description_observer(
new rtc::RefCountedObject<MockSetSessionDescriptionObserver>());
caller->pc()->SetRemoteDescription(
caller_set_remote_description_observer,
CloneSessionDescription(callee->pc()->current_local_description())
.release());
EXPECT_TRUE_WAIT(caller_set_remote_description_observer->called(),
kWaitTimeout);
EXPECT_EQ(PeerConnection::kStable, caller->signaling_state());
EXPECT_EQ(PeerConnection::kStable, callee->signaling_state());
}
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionCloseBeforeCreatingOffer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->Close();
caller->pc()->SetLocalDescription(observer);
// The operation should fail asynchronously.
EXPECT_FALSE(observer->called());
EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout);
EXPECT_FALSE(observer->result());
// This did not affect the signaling state.
EXPECT_EQ(PeerConnection::kClosed, caller->pc()->signaling_state());
EXPECT_EQ(
"SetLocalDescription failed to create session description - "
"SetLocalDescription called when PeerConnection is closed.",
observer->error());
}
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionCloseWhileCreatingOffer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(observer);
caller->pc()->Close();
// The operation should fail asynchronously.
EXPECT_FALSE(observer->called());
EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout);
EXPECT_FALSE(observer->result());
// This did not affect the signaling state.
EXPECT_EQ(PeerConnection::kClosed, caller->pc()->signaling_state());
EXPECT_EQ(
"SetLocalDescription failed to create session description - "
"CreateOffer failed because the session was shut down",
observer->error());
}
INSTANTIATE_TEST_SUITE_P(PeerConnectionSignalingTest, INSTANTIATE_TEST_SUITE_P(PeerConnectionSignalingTest,
PeerConnectionSignalingTest, PeerConnectionSignalingTest,
Values(SdpSemantics::kPlanB, Values(SdpSemantics::kPlanB,

View File

@ -271,6 +271,10 @@ class MockCreateSessionDescriptionObserver
class MockSetSessionDescriptionObserver class MockSetSessionDescriptionObserver
: public webrtc::SetSessionDescriptionObserver { : public webrtc::SetSessionDescriptionObserver {
public: public:
static rtc::scoped_refptr<MockSetSessionDescriptionObserver> Create() {
return new rtc::RefCountedObject<MockSetSessionDescriptionObserver>();
}
MockSetSessionDescriptionObserver() MockSetSessionDescriptionObserver()
: called_(false), : called_(false),
error_("MockSetSessionDescriptionObserver not called") {} error_("MockSetSessionDescriptionObserver not called") {}