Added signaling state to PeerConnection

This commit is contained in:
Paul-Louis Ageneau
2020-10-31 18:30:14 +01:00
parent a3cc74c8f1
commit 0c47c66bb1
5 changed files with 192 additions and 65 deletions

View File

@ -34,8 +34,8 @@ namespace rtc {
class Description { class Description {
public: public:
enum class Type { Unspec = 0, Offer = 1, Answer = 2 }; enum class Type { Unspec, Offer, Answer, Pranswer, Rollback };
enum class Role { ActPass = 0, Passive = 1, Active = 2 }; enum class Role { ActPass, Passive, Active };
enum class Direction { SendOnly, RecvOnly, SendRecv, Inactive, Unknown }; enum class Direction { SendOnly, RecvOnly, SendRecv, Inactive, Unknown };
Description(const string &sdp, const string &typeString = ""); Description(const string &sdp, const string &typeString = "");
@ -45,7 +45,6 @@ public:
Type type() const; Type type() const;
string typeString() const; string typeString() const;
Role role() const; Role role() const;
string roleString() const;
string bundleMid() const; string bundleMid() const;
string iceUfrag() const; string iceUfrag() const;
string icePwd() const; string icePwd() const;
@ -193,6 +192,9 @@ public:
Application *application(); Application *application();
static Type stringToType(const string &typeString);
static string typeToString(Type type);
private: private:
std::optional<Candidate> defaultCandidate() const; std::optional<Candidate> defaultCandidate() const;
std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir); std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
@ -214,14 +216,12 @@ private:
// Candidates // Candidates
std::vector<Candidate> mCandidates; std::vector<Candidate> mCandidates;
bool mEnded = false; bool mEnded = false;
static Type stringToType(const string &typeString);
static string typeToString(Type type);
static string roleToString(Role role);
}; };
} // namespace rtc } // namespace rtc
std::ostream &operator<<(std::ostream &out, const rtc::Description &description); std::ostream &operator<<(std::ostream &out, const rtc::Description &description);
std::ostream &operator<<(std::ostream &out, rtc::Description::Type type);
std::ostream &operator<<(std::ostream &out, rtc::Description::Role role);
#endif #endif

View File

@ -67,6 +67,14 @@ public:
Complete = RTC_GATHERING_COMPLETE Complete = RTC_GATHERING_COMPLETE
}; };
enum class SignalingState : int {
Stable = RTC_SIGNALING_STABLE,
HaveLocalOffer = RTC_SIGNALING_HAVE_LOCAL_OFFER,
HaveRemoteOffer = RTC_SIGNALING_HAVE_REMOTE_OFFER,
HaveLocalPranswer = RTC_SIGNALING_HAVE_LOCAL_PRANSWER,
HaveRemotePranswer = RTC_SIGNALING_HAVE_REMOTE_PRANSWER,
} rtcSignalingState;
PeerConnection(void); PeerConnection(void);
PeerConnection(const Configuration &config); PeerConnection(const Configuration &config);
~PeerConnection(); ~PeerConnection();
@ -76,6 +84,7 @@ public:
const Configuration *config() const; const Configuration *config() const;
State state() const; State state() const;
GatheringState gatheringState() const; GatheringState gatheringState() const;
SignalingState signalingState() const;
bool hasLocalDescription() const; bool hasLocalDescription() const;
bool hasRemoteDescription() const; bool hasRemoteDescription() const;
bool hasMedia() const; bool hasMedia() const;
@ -83,8 +92,9 @@ public:
std::optional<Description> remoteDescription() const; std::optional<Description> remoteDescription() const;
std::optional<string> localAddress() const; std::optional<string> localAddress() const;
std::optional<string> remoteAddress() const; std::optional<string> remoteAddress() const;
bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
void setLocalDescription(); void setLocalDescription(Description::Type type = Description::Type::Unspec);
void setRemoteDescription(Description description); void setRemoteDescription(Description description);
void addRemoteCandidate(Candidate candidate); void addRemoteCandidate(Candidate candidate);
@ -100,6 +110,7 @@ public:
void onLocalCandidate(std::function<void(Candidate candidate)> callback); void onLocalCandidate(std::function<void(Candidate candidate)> callback);
void onStateChange(std::function<void(State state)> callback); void onStateChange(std::function<void(State state)> callback);
void onGatheringStateChange(std::function<void(GatheringState state)> callback); void onGatheringStateChange(std::function<void(GatheringState state)> callback);
void onSignalingStateChange(std::function<void(SignalingState state)> callback);
// Stats // Stats
void clearStats(); void clearStats();
@ -111,9 +122,6 @@ public:
std::shared_ptr<Track> addTrack(Description::Media description); std::shared_ptr<Track> addTrack(Description::Media description);
void onTrack(std::function<void(std::shared_ptr<Track> track)> callback); void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
// libnice only
bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
private: private:
std::shared_ptr<IceTransport> initIceTransport(Description::Role role); std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
std::shared_ptr<DtlsTransport> initDtlsTransport(); std::shared_ptr<DtlsTransport> initDtlsTransport();
@ -143,6 +151,7 @@ private:
void triggerTrack(std::shared_ptr<Track> track); void triggerTrack(std::shared_ptr<Track> track);
bool changeState(State state); bool changeState(State state);
bool changeGatheringState(GatheringState state); bool changeGatheringState(GatheringState state);
bool changeSignalingState(SignalingState state);
void resetCallbacks(); void resetCallbacks();
@ -168,6 +177,7 @@ private:
std::atomic<State> mState; std::atomic<State> mState;
std::atomic<GatheringState> mGatheringState; std::atomic<GatheringState> mGatheringState;
std::atomic<SignalingState> mSignalingState;
std::atomic<bool> mNegociationNeeded; std::atomic<bool> mNegociationNeeded;
synchronized_callback<std::shared_ptr<DataChannel>> mDataChannelCallback; synchronized_callback<std::shared_ptr<DataChannel>> mDataChannelCallback;
@ -175,12 +185,14 @@ private:
synchronized_callback<Candidate> mLocalCandidateCallback; synchronized_callback<Candidate> mLocalCandidateCallback;
synchronized_callback<State> mStateChangeCallback; synchronized_callback<State> mStateChangeCallback;
synchronized_callback<GatheringState> mGatheringStateChangeCallback; synchronized_callback<GatheringState> mGatheringStateChangeCallback;
synchronized_callback<SignalingState> mSignalingStateChangeCallback;
synchronized_callback<std::shared_ptr<Track>> mTrackCallback; synchronized_callback<std::shared_ptr<Track>> mTrackCallback;
}; };
} // namespace rtc } // namespace rtc
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state); std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state);
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state); std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state);
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::SignalingState state);
#endif #endif

View File

@ -59,6 +59,14 @@ typedef enum {
RTC_GATHERING_COMPLETE = 2 RTC_GATHERING_COMPLETE = 2
} rtcGatheringState; } rtcGatheringState;
typedef enum {
RTC_SIGNALING_STABLE = 0,
RTC_SIGNALING_HAVE_LOCAL_OFFER = 1,
RTC_SIGNALING_HAVE_REMOTE_OFFER = 2,
RTC_SIGNALING_HAVE_LOCAL_PRANSWER = 3,
RTC_SIGNALING_HAVE_REMOTE_PRANSWER = 4,
} rtcSignalingState;
typedef enum { // Don't change, it must match plog severity typedef enum { // Don't change, it must match plog severity
RTC_LOG_NONE = 0, RTC_LOG_NONE = 0,
RTC_LOG_FATAL = 1, RTC_LOG_FATAL = 1,

View File

@ -26,6 +26,7 @@
#include <iostream> #include <iostream>
#include <random> #include <random>
#include <sstream> #include <sstream>
#include <unordered_map>
using std::shared_ptr; using std::shared_ptr;
using std::size_t; using std::size_t;
@ -767,12 +768,14 @@ Description::Video::Video(string mid, Direction dir)
: Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {} : Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
Description::Type Description::stringToType(const string &typeString) { Description::Type Description::stringToType(const string &typeString) {
if (typeString == "offer") using TypeMap_t = std::unordered_map<string, Type>;
return Type::Offer; static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
else if (typeString == "answer") {"offer", Type::Offer},
return Type::Answer; {"answer", Type::Pranswer},
else {"pranswer", Type::Pranswer},
return Type::Unspec; {"rollback", Type::Rollback}};
auto it = TypeMap.find(typeString);
return it != TypeMap.end() ? it->second : Type::Unspec;
} }
string Description::typeToString(Type type) { string Description::typeToString(Type type) {
@ -781,19 +784,12 @@ string Description::typeToString(Type type) {
return "offer"; return "offer";
case Type::Answer: case Type::Answer:
return "answer"; return "answer";
case Type::Pranswer:
return "pranswer";
case Type::Rollback:
return "rollback";
default: default:
return ""; return "unknown";
}
}
string Description::roleToString(Role role) {
switch (role) {
case Role::Active:
return "active";
case Role::Passive:
return "passive";
default:
return "actpass";
} }
} }
@ -802,3 +798,25 @@ string Description::roleToString(Role role) {
std::ostream &operator<<(std::ostream &out, const rtc::Description &description) { std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
return out << std::string(description); return out << std::string(description);
} }
std::ostream &operator<<(std::ostream &out, rtc::Description::Type type) {
return out << rtc::Description::typeToString(type);
}
std::ostream &operator<<(std::ostream &out, rtc::Description::Role role) {
using Role = rtc::Description::Role;
const char *str;
switch (role) {
case Role::Active:
str = "active";
break;
case Role::Passive:
str = "passive";
break;
default:
str = "actpass";
break;
}
return out << str;
}

View File

@ -44,7 +44,8 @@ PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
PeerConnection::PeerConnection(const Configuration &config) PeerConnection::PeerConnection(const Configuration &config)
: mConfig(config), mCertificate(make_certificate()), mProcessor(std::make_unique<Processor>()), : mConfig(config), mCertificate(make_certificate()), mProcessor(std::make_unique<Processor>()),
mState(State::New), mGatheringState(GatheringState::New), mNegociationNeeded(false) { mState(State::New), mGatheringState(GatheringState::New),
mSignalingState(SignalingState::Stable), mNegociationNeeded(false) {
PLOG_VERBOSE << "Creating PeerConnection"; PLOG_VERBOSE << "Creating PeerConnection";
if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd) if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
@ -74,6 +75,8 @@ PeerConnection::State PeerConnection::state() const { return mState; }
PeerConnection::GatheringState PeerConnection::gatheringState() const { return mGatheringState; } PeerConnection::GatheringState PeerConnection::gatheringState() const { return mGatheringState; }
PeerConnection::SignalingState PeerConnection::signalingState() const { return mSignalingState; }
std::optional<Description> PeerConnection::localDescription() const { std::optional<Description> PeerConnection::localDescription() const {
std::lock_guard lock(mLocalDescriptionMutex); std::lock_guard lock(mLocalDescriptionMutex);
return mLocalDescription; return mLocalDescription;
@ -99,7 +102,7 @@ bool PeerConnection::hasMedia() const {
return local && local->hasAudioOrVideo(); return local && local->hasAudioOrVideo();
} }
void PeerConnection::setLocalDescription() { void PeerConnection::setLocalDescription(Description::Type type) {
PLOG_VERBOSE << "Setting local description"; PLOG_VERBOSE << "Setting local description";
if (!mNegociationNeeded.exchange(false)) { if (!mNegociationNeeded.exchange(false)) {
@ -107,12 +110,29 @@ void PeerConnection::setLocalDescription() {
return; return;
} }
// Guess the description type if unspecified
if (type == Description::Type::Unspec) {
if (mSignalingState == SignalingState::HaveRemoteOffer)
type = Description::Type::Answer;
else
type = Description::Type::Offer;
}
auto iceTransport = std::atomic_load(&mIceTransport);
if (!iceTransport) {
if (type != Description::Type::Offer) {
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of // RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
// setup:actpass. // setup:actpass.
// See https://tools.ietf.org/html/rfc5763#section-5 // See https://tools.ietf.org/html/rfc5763#section-5
auto iceTransport = initIceTransport(Description::Role::ActPass); if (!iceTransport)
Description localDescription = iceTransport->getLocalDescription(Description::Type::Offer); iceTransport = initIceTransport(Description::Role::ActPass);
}
}
Description localDescription = iceTransport->getLocalDescription(type);
processLocalDescription(localDescription); processLocalDescription(localDescription);
if (mGatheringState == GatheringState::New)
iceTransport->gatherLocalCandidates(); iceTransport->gatherLocalCandidates();
} }
@ -143,21 +163,40 @@ void PeerConnection::setRemoteDescription(Description description) {
throw std::logic_error("Got the local description as remote description"); throw std::logic_error("Got the local description as remote description");
} }
description.hintType(hasLocalDescription() ? Description::Type::Answer // Get the new signaling state
: Description::Type::Offer); SignalingState signalingState = mSignalingState.load();
SignalingState newSignalingState;
switch (signalingState) {
case SignalingState::Stable:
description.hintType(Description::Type::Offer);
if (description.type() != Description::Type::Offer) {
LOG_ERROR << "Unexpected remote " << description.type()
<< " description in signaling state " << signalingState << ", expected offer";
std::ostringstream oss;
oss << "Unexpected remote " << description.type() << " description";
throw std::logic_error(oss.str());
}
newSignalingState = SignalingState::HaveRemoteOffer;
break;
// If there is no remote description, this is the first negociation case SignalingState::HaveLocalOffer:
// Check it is what we expect case SignalingState::HaveRemotePranswer:
if (!hasRemoteDescription()) { description.hintType(Description::Type::Answer);
if (description.type() == Description::Type::Offer) { if (description.type() != Description::Type::Answer ||
if (hasLocalDescription()) { description.type() != Description::Type::Pranswer) {
PLOG_ERROR << "Got a remote offer description while an answer was expected"; LOG_ERROR << "Unexpected remote " << description.type()
throw std::logic_error("Got an unexpected remote offer description"); << " description in signaling state " << signalingState
} << ", expected answer";
} else { // Answer std::ostringstream oss;
PLOG_ERROR << "Got a remote answer description while an offer was expected"; oss << "Unexpected remote " << description.type() << " description";
throw std::logic_error("Got an unexpected remote answer description"); throw std::logic_error(oss.str());
} }
newSignalingState = SignalingState::Stable;
break;
default:
LOG_ERROR << "Unexpected remote description in signaling state " << signalingState;
throw std::logic_error("Unexpected remote description");
} }
// Candidates will be added at the end, extract them for now // Candidates will be added at the end, extract them for now
@ -168,6 +207,13 @@ void PeerConnection::setRemoteDescription(Description description) {
iceTransport = initIceTransport(Description::Role::ActPass); iceTransport = initIceTransport(Description::Role::ActPass);
iceTransport->setRemoteDescription(description); iceTransport->setRemoteDescription(description);
if (description.hasApplication()) {
if (auto current = remoteDescription(); current && !current->hasApplication())
if (auto dtlsTransport = std::atomic_load(&mDtlsTransport);
dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
initSctpTransport();
}
{ {
// Set as remote description // Set as remote description
std::lock_guard lock(mRemoteDescriptionMutex); std::lock_guard lock(mRemoteDescriptionMutex);
@ -181,11 +227,12 @@ void PeerConnection::setRemoteDescription(Description description) {
mRemoteDescription->addCandidate(candidate); mRemoteDescription->addCandidate(candidate);
} }
changeSignalingState(newSignalingState);
if (description.type() == Description::Type::Offer) { if (description.type() == Description::Type::Offer) {
// This is an offer, we need to answer // This is an offer, we need to answer
Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer); mNegociationNeeded = true;
processLocalDescription(localDescription); setLocalDescription(Description::Type::Answer);
iceTransport->gatherLocalCandidates();
} else { } else {
// This is an answer // This is an answer
auto sctpTransport = std::atomic_load(&mSctpTransport); auto sctpTransport = std::atomic_load(&mSctpTransport);
@ -204,6 +251,8 @@ void PeerConnection::setRemoteDescription(Description description) {
} }
std::swap(mDataChannels, newDataChannels); std::swap(mDataChannels, newDataChannels);
} }
changeSignalingState(SignalingState::Stable);
} }
for (const auto &candidate : remoteCandidates) for (const auto &candidate : remoteCandidates)
@ -261,7 +310,7 @@ shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string prot
if (transport->state() == SctpTransport::State::Connected) if (transport->state() == SctpTransport::State::Connected)
channel->open(transport); channel->open(transport);
// Renegociation is needed if the current local description does not have application // Renegociation is needed iff the current local description does not have application
std::lock_guard lock(mLocalDescriptionMutex); std::lock_guard lock(mLocalDescriptionMutex);
if (!mLocalDescription || !mLocalDescription->hasApplication()) if (!mLocalDescription || !mLocalDescription->hasApplication())
mNegociationNeeded = true; mNegociationNeeded = true;
@ -297,6 +346,10 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
mGatheringStateChangeCallback = callback; mGatheringStateChangeCallback = callback;
} }
void PeerConnection::onSignalingStateChange(std::function<void(SignalingState state)> callback) {
mSignalingStateChangeCallback = callback;
}
std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) { std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) {
if (hasLocalDescription()) if (hasLocalDescription())
throw std::logic_error("Tracks must be created before local description"); throw std::logic_error("Tracks must be created before local description");
@ -401,7 +454,7 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
switch (state) { switch (state) {
case DtlsTransport::State::Connected: case DtlsTransport::State::Connected:
if (auto local = localDescription(); local && local->hasApplication()) if (auto remote = remoteDescription(); remote && remote->hasApplication())
initSctpTransport(); initSctpTransport();
else else
changeState(State::Connected); changeState(State::Connected);
@ -910,6 +963,16 @@ bool PeerConnection::changeGatheringState(GatheringState state) {
return true; return true;
} }
bool PeerConnection::changeSignalingState(SignalingState state) {
if (mSignalingState.exchange(state) != state) {
std::ostringstream s;
s << state;
PLOG_INFO << "Changed signaling state to " << s.str();
mProcessor->enqueue([this, state] { mSignalingStateChangeCallback(state); });
}
return true;
}
void PeerConnection::resetCallbacks() { void PeerConnection::resetCallbacks() {
// Unregister all callbacks // Unregister all callbacks
mDataChannelCallback = nullptr; mDataChannelCallback = nullptr;
@ -957,7 +1020,7 @@ std::optional<std::chrono::milliseconds> PeerConnection::rtt() {
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state) { std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state) {
using State = rtc::PeerConnection::State; using State = rtc::PeerConnection::State;
std::string str; const char *str;
switch (state) { switch (state) {
case State::New: case State::New:
str = "new"; str = "new";
@ -986,13 +1049,13 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &st
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state) { std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state) {
using GatheringState = rtc::PeerConnection::GatheringState; using GatheringState = rtc::PeerConnection::GatheringState;
std::string str; const char *str;
switch (state) { switch (state) {
case GatheringState::New: case GatheringState::New:
str = "new"; str = "new";
break; break;
case GatheringState::InProgress: case GatheringState::InProgress:
str = "in_progress"; str = "in-progress";
break; break;
case GatheringState::Complete: case GatheringState::Complete:
str = "complete"; str = "complete";
@ -1003,3 +1066,29 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::Gathering
} }
return out << str; return out << str;
} }
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::SignalingState &state) {
using SignalingState = rtc::PeerConnection::SignalingState;
const char *str;
switch (state) {
case SignalingState::Stable:
str = "stable";
break;
case SignalingState::HaveLocalOffer:
str = "have-local-offer";
break;
case SignalingState::HaveRemoteOffer:
str = "have-remote-offer";
break;
case SignalingState::HaveLocalPranswer:
str = "have-local-pranswer";
break;
case SignalingState::HaveRemotePranswer:
str = "have-remote-pranswer";
break;
default:
str = "unknown";
break;
}
return out << str;
}