diff --git a/api/dtlstransportinterface.h b/api/dtlstransportinterface.h index dff4f5ea3d..bff67520f0 100644 --- a/api/dtlstransportinterface.h +++ b/api/dtlstransportinterface.h @@ -11,18 +11,57 @@ #ifndef API_DTLSTRANSPORTINTERFACE_H_ #define API_DTLSTRANSPORTINTERFACE_H_ +#include "api/rtcerror.h" #include "rtc_base/refcount.h" namespace webrtc { +// States of a DTLS transport, corresponding to the JS API specification. +// http://w3c.github.io/webrtc-pc/#dom-rtcdtlstransportstate +enum class DtlsTransportState { + kNew, // Has not started negotiating yet. + kConnecting, // In the process of negotiating a secure connection. + kConnected, // Completed negotiation and verified fingerprints. + kClosed, // Intentionally closed. + kFailed // Failure due to an error or failing to verify a remote fingerprint. +}; + +// This object gives snapshot information about the changeable state of a +// DTLSTransport. +class DtlsTransportInformation { + public: + explicit DtlsTransportInformation(DtlsTransportState state) : state_(state) {} + DtlsTransportState state() const { return state_; } + // TODO(hta): Add remote certificate access + private: + DtlsTransportState state_; +}; + +class DtlsTransportObserverInterface { + public: + // This callback carries information about the state of the transport. + // The argument is a pass-by-value snapshot of the state. + virtual void OnStateChange(DtlsTransportInformation info) = 0; + // This callback is called when an error occurs, causing the transport + // to go to the kFailed state. + virtual void OnError(RTCError error) = 0; + + protected: + virtual ~DtlsTransportObserverInterface() = default; +}; + // A DTLS transport, as represented to the outside world. -// Its role is to report state changes and errors, and make sure information -// about remote certificates is available. +// This object is created on the signaling thread, and can only be +// accessed on that thread. +// References can be held by other threads, and destruction can therefore +// be initiated by other threads. class DtlsTransportInterface : public rtc::RefCountInterface { public: - // TODO(hta): Need a notifier interface to transmit state changes and - // error events. The generic NotifierInterface of mediasteraminterface.h - // may be suitable, or may be copyable. + // These functions can only be called from the signalling thread. + virtual DtlsTransportInformation Information() = 0; + // Observer management. + virtual void RegisterObserver(DtlsTransportObserverInterface* observer) = 0; + virtual void UnregisterObserver() = 0; }; } // namespace webrtc diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 268176c6cf..7736b2fbd6 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -249,6 +249,7 @@ if (rtc_include_tests) { "channel_unittest.cc", "channelmanager_unittest.cc", "dtlssrtptransport_unittest.cc", + "dtlstransport_unittest.cc", "jseptransport_unittest.cc", "jseptransportcontroller_unittest.cc", "mediasession_unittest.cc", diff --git a/pc/dtlstransport.cc b/pc/dtlstransport.cc index dd95baefe8..6e84d64e9c 100644 --- a/pc/dtlstransport.cc +++ b/pc/dtlstransport.cc @@ -14,10 +14,81 @@ namespace webrtc { +namespace { + +DtlsTransportState TranslateState(cricket::DtlsTransportState internal_state) { + switch (internal_state) { + case cricket::DTLS_TRANSPORT_NEW: + return DtlsTransportState::kNew; + break; + case cricket::DTLS_TRANSPORT_CONNECTING: + return DtlsTransportState::kConnecting; + break; + case cricket::DTLS_TRANSPORT_CONNECTED: + return DtlsTransportState::kConnected; + break; + case cricket::DTLS_TRANSPORT_CLOSED: + return DtlsTransportState::kClosed; + break; + case cricket::DTLS_TRANSPORT_FAILED: + return DtlsTransportState::kFailed; + break; + } +} + +} // namespace + +// Implementation of DtlsTransportInterface DtlsTransport::DtlsTransport( std::unique_ptr internal) - : internal_dtls_transport_(std::move(internal)) { + : signaling_thread_(rtc::Thread::Current()), + internal_dtls_transport_(std::move(internal)) { RTC_DCHECK(internal_dtls_transport_.get()); + internal_dtls_transport_->SignalDtlsState.connect( + this, &DtlsTransport::OnInternalDtlsState); +} + +DtlsTransport::~DtlsTransport() { + // We depend on the signaling thread to call Clear() before dropping + // its last reference to this object. + RTC_DCHECK(signaling_thread_->IsCurrent() || !internal_dtls_transport_); +} + +DtlsTransportInformation DtlsTransport::Information() { + RTC_DCHECK(signaling_thread_->IsCurrent()); + if (internal()) { + return DtlsTransportInformation(TranslateState(internal()->dtls_state())); + } else { + return DtlsTransportInformation(DtlsTransportState::kClosed); + } +} + +void DtlsTransport::RegisterObserver(DtlsTransportObserverInterface* observer) { + RTC_DCHECK(signaling_thread_->IsCurrent()); + RTC_DCHECK(observer); + observer_ = observer; +} + +void DtlsTransport::UnregisterObserver() { + RTC_DCHECK(signaling_thread_->IsCurrent()); + observer_ = nullptr; +} + +// Internal functions +void DtlsTransport::Clear() { + RTC_DCHECK(signaling_thread_->IsCurrent()); + internal_dtls_transport_.reset(); +} + +void DtlsTransport::OnInternalDtlsState( + cricket::DtlsTransportInternal* transport, + cricket::DtlsTransportState state) { + RTC_DCHECK(signaling_thread_->IsCurrent()); + RTC_DCHECK(transport == internal()); + RTC_DCHECK(state == internal()->dtls_state()); + if (observer_) { + observer_->OnStateChange(Information()); + } } } // namespace webrtc diff --git a/pc/dtlstransport.h b/pc/dtlstransport.h index 03901c33ff..b3f16378f2 100644 --- a/pc/dtlstransport.h +++ b/pc/dtlstransport.h @@ -15,21 +15,41 @@ #include "api/dtlstransportinterface.h" #include "p2p/base/dtlstransport.h" +#include "rtc_base/asyncinvoker.h" namespace webrtc { // This implementation wraps a cricket::DtlsTransport, and takes // ownership of it. -class DtlsTransport : public DtlsTransportInterface { +class DtlsTransport : public DtlsTransportInterface, + public sigslot::has_slots<> { public: + // This object must be constructed on the signaling thread. explicit DtlsTransport( std::unique_ptr internal); + + DtlsTransportInformation Information() override; + void RegisterObserver(DtlsTransportObserverInterface* observer) override; + void UnregisterObserver() override; + void Clear(); + cricket::DtlsTransportInternal* internal() { return internal_dtls_transport_.get(); } - void clear() { internal_dtls_transport_.reset(); } + + const cricket::DtlsTransportInternal* internal() const { + return internal_dtls_transport_.get(); + } + + protected: + ~DtlsTransport(); private: + void OnInternalDtlsState(cricket::DtlsTransportInternal* transport, + cricket::DtlsTransportState state); + + DtlsTransportObserverInterface* observer_ = nullptr; + rtc::Thread* signaling_thread_; std::unique_ptr internal_dtls_transport_; }; diff --git a/pc/dtlstransport_unittest.cc b/pc/dtlstransport_unittest.cc new file mode 100644 index 0000000000..62c5e24c7a --- /dev/null +++ b/pc/dtlstransport_unittest.cc @@ -0,0 +1,97 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "pc/dtlstransport.h" + +#include +#include + +#include "absl/memory/memory.h" +#include "p2p/base/fakedtlstransport.h" +#include "rtc_base/gunit.h" +#include "test/gmock.h" +#include "test/gtest.h" + +constexpr int kDefaultTimeout = 1000; // milliseconds + +using cricket::FakeDtlsTransport; +using ::testing::ElementsAre; + +namespace webrtc { + +class TestDtlsTransportObserver : public DtlsTransportObserverInterface { + public: + void OnStateChange(DtlsTransportInformation info) override { + state_change_called_ = true; + states_.push_back(info.state()); + } + + void OnError(RTCError error) override {} + + bool state_change_called_ = false; + std::vector states_; +}; + +class DtlsTransportTest : public testing::Test { + public: + DtlsTransport* transport() { return transport_.get(); } + DtlsTransportObserverInterface* observer() { return &observer_; } + + void CreateTransport() { + auto cricket_transport = absl::make_unique( + "audio", cricket::ICE_CANDIDATE_COMPONENT_RTP); + transport_ = + new rtc::RefCountedObject(std::move(cricket_transport)); + } + + void CompleteDtlsHandshake() { + auto fake_dtls1 = static_cast(transport_->internal()); + auto fake_dtls2 = absl::make_unique( + "audio", cricket::ICE_CANDIDATE_COMPONENT_RTP); + auto cert1 = rtc::RTCCertificate::Create(absl::WrapUnique( + rtc::SSLIdentity::Generate("session1", rtc::KT_DEFAULT))); + fake_dtls1->SetLocalCertificate(cert1); + auto cert2 = rtc::RTCCertificate::Create(absl::WrapUnique( + rtc::SSLIdentity::Generate("session1", rtc::KT_DEFAULT))); + fake_dtls2->SetLocalCertificate(cert2); + fake_dtls1->SetDestination(fake_dtls2.get()); + } + + rtc::scoped_refptr transport_; + TestDtlsTransportObserver observer_; +}; + +TEST_F(DtlsTransportTest, CreateClearDelete) { + auto cricket_transport = absl::make_unique( + "audio", cricket::ICE_CANDIDATE_COMPONENT_RTP); + rtc::scoped_refptr webrtc_transport = + new rtc::RefCountedObject(std::move(cricket_transport)); + ASSERT_TRUE(webrtc_transport->internal()); + ASSERT_EQ(DtlsTransportState::kNew, webrtc_transport->Information().state()); + webrtc_transport->Clear(); + ASSERT_FALSE(webrtc_transport->internal()); + ASSERT_EQ(DtlsTransportState::kClosed, + webrtc_transport->Information().state()); +} + +TEST_F(DtlsTransportTest, EventsObservedWhenConnecting) { + CreateTransport(); + transport()->RegisterObserver(observer()); + CompleteDtlsHandshake(); + ASSERT_TRUE_WAIT(observer_.state_change_called_, kDefaultTimeout); + EXPECT_THAT( + observer_.states_, + ElementsAre( // FakeDtlsTransport doesn't signal the "connecting" state. + // TODO(hta): fix FakeDtlsTransport or file bug on it. + // DtlsTransportState::kConnecting, + DtlsTransportState::kConnected)); +} + +} // namespace webrtc diff --git a/pc/jseptransport.cc b/pc/jseptransport.cc index eddbbce5e1..3372f1974f 100644 --- a/pc/jseptransport.cc +++ b/pc/jseptransport.cc @@ -141,9 +141,9 @@ JsepTransport::~JsepTransport() { } // Clear all DtlsTransports. There may be pointers to these from // other places, so we can't assume they'll be deleted by the destructor. - rtp_dtls_transport_->clear(); + rtp_dtls_transport_->Clear(); if (rtcp_dtls_transport_) { - rtcp_dtls_transport_->clear(); + rtcp_dtls_transport_->Clear(); } }