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

View File

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

View File

@ -59,6 +59,14 @@ typedef enum {
RTC_GATHERING_COMPLETE = 2
} 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
RTC_LOG_NONE = 0,
RTC_LOG_FATAL = 1,

View File

@ -26,6 +26,7 @@
#include <iostream>
#include <random>
#include <sstream>
#include <unordered_map>
using std::shared_ptr;
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) {}
Description::Type Description::stringToType(const string &typeString) {
if (typeString == "offer")
return Type::Offer;
else if (typeString == "answer")
return Type::Answer;
else
return Type::Unspec;
using TypeMap_t = std::unordered_map<string, Type>;
static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
{"offer", Type::Offer},
{"answer", Type::Pranswer},
{"pranswer", Type::Pranswer},
{"rollback", Type::Rollback}};
auto it = TypeMap.find(typeString);
return it != TypeMap.end() ? it->second : Type::Unspec;
}
string Description::typeToString(Type type) {
@ -781,19 +784,12 @@ string Description::typeToString(Type type) {
return "offer";
case Type::Answer:
return "answer";
case Type::Pranswer:
return "pranswer";
case Type::Rollback:
return "rollback";
default:
return "";
}
}
string Description::roleToString(Role role) {
switch (role) {
case Role::Active:
return "active";
case Role::Passive:
return "passive";
default:
return "actpass";
return "unknown";
}
}
@ -802,3 +798,25 @@ string Description::roleToString(Role role) {
std::ostream &operator<<(std::ostream &out, const rtc::Description &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)
: 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";
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::SignalingState PeerConnection::signalingState() const { return mSignalingState; }
std::optional<Description> PeerConnection::localDescription() const {
std::lock_guard lock(mLocalDescriptionMutex);
return mLocalDescription;
@ -99,7 +102,7 @@ bool PeerConnection::hasMedia() const {
return local && local->hasAudioOrVideo();
}
void PeerConnection::setLocalDescription() {
void PeerConnection::setLocalDescription(Description::Type type) {
PLOG_VERBOSE << "Setting local description";
if (!mNegociationNeeded.exchange(false)) {
@ -107,13 +110,30 @@ void PeerConnection::setLocalDescription() {
return;
}
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
// setup:actpass.
// See https://tools.ietf.org/html/rfc5763#section-5
auto iceTransport = initIceTransport(Description::Role::ActPass);
Description localDescription = iceTransport->getLocalDescription(Description::Type::Offer);
// 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
// setup:actpass.
// See https://tools.ietf.org/html/rfc5763#section-5
if (!iceTransport)
iceTransport = initIceTransport(Description::Role::ActPass);
}
}
Description localDescription = iceTransport->getLocalDescription(type);
processLocalDescription(localDescription);
iceTransport->gatherLocalCandidates();
if (mGatheringState == GatheringState::New)
iceTransport->gatherLocalCandidates();
}
void PeerConnection::setRemoteDescription(Description description) {
@ -143,21 +163,40 @@ void PeerConnection::setRemoteDescription(Description description) {
throw std::logic_error("Got the local description as remote description");
}
description.hintType(hasLocalDescription() ? Description::Type::Answer
: Description::Type::Offer);
// If there is no remote description, this is the first negociation
// Check it is what we expect
if (!hasRemoteDescription()) {
if (description.type() == Description::Type::Offer) {
if (hasLocalDescription()) {
PLOG_ERROR << "Got a remote offer description while an answer was expected";
throw std::logic_error("Got an unexpected remote offer description");
}
} else { // Answer
PLOG_ERROR << "Got a remote answer description while an offer was expected";
throw std::logic_error("Got an unexpected remote answer description");
// Get the new signaling state
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;
case SignalingState::HaveLocalOffer:
case SignalingState::HaveRemotePranswer:
description.hintType(Description::Type::Answer);
if (description.type() != Description::Type::Answer ||
description.type() != Description::Type::Pranswer) {
LOG_ERROR << "Unexpected remote " << description.type()
<< " description in signaling state " << signalingState
<< ", expected answer";
std::ostringstream oss;
oss << "Unexpected remote " << description.type() << " 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
@ -168,12 +207,19 @@ void PeerConnection::setRemoteDescription(Description description) {
iceTransport = initIceTransport(Description::Role::ActPass);
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
std::lock_guard lock(mRemoteDescriptionMutex);
std::vector<Candidate> existingCandidates;
if(mRemoteDescription)
if (mRemoteDescription)
existingCandidates = mRemoteDescription->extractCandidates();
mRemoteDescription.emplace(std::move(description));
@ -181,11 +227,12 @@ void PeerConnection::setRemoteDescription(Description description) {
mRemoteDescription->addCandidate(candidate);
}
changeSignalingState(newSignalingState);
if (description.type() == Description::Type::Offer) {
// This is an offer, we need to answer
Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer);
processLocalDescription(localDescription);
iceTransport->gatherLocalCandidates();
mNegociationNeeded = true;
setLocalDescription(Description::Type::Answer);
} else {
// This is an answer
auto sctpTransport = std::atomic_load(&mSctpTransport);
@ -204,6 +251,8 @@ void PeerConnection::setRemoteDescription(Description description) {
}
std::swap(mDataChannels, newDataChannels);
}
changeSignalingState(SignalingState::Stable);
}
for (const auto &candidate : remoteCandidates)
@ -261,7 +310,7 @@ shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string prot
if (transport->state() == SctpTransport::State::Connected)
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);
if (!mLocalDescription || !mLocalDescription->hasApplication())
mNegociationNeeded = true;
@ -297,6 +346,10 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
mGatheringStateChangeCallback = callback;
}
void PeerConnection::onSignalingStateChange(std::function<void(SignalingState state)> callback) {
mSignalingStateChangeCallback = callback;
}
std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) {
if (hasLocalDescription())
throw std::logic_error("Tracks must be created before local description");
@ -401,7 +454,7 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
switch (state) {
case DtlsTransport::State::Connected:
if (auto local = localDescription(); local && local->hasApplication())
if (auto remote = remoteDescription(); remote && remote->hasApplication())
initSctpTransport();
else
changeState(State::Connected);
@ -833,9 +886,9 @@ void PeerConnection::processLocalDescription(Description description) {
{
// Set as local description
std::lock_guard lock(mLocalDescriptionMutex);
std::vector<Candidate> existingCandidates;
if(mLocalDescription)
if (mLocalDescription)
existingCandidates = mLocalDescription->extractCandidates();
mLocalDescription.emplace(std::move(description));
@ -910,6 +963,16 @@ bool PeerConnection::changeGatheringState(GatheringState state) {
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() {
// Unregister all callbacks
mDataChannelCallback = nullptr;
@ -957,7 +1020,7 @@ std::optional<std::chrono::milliseconds> PeerConnection::rtt() {
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state) {
using State = rtc::PeerConnection::State;
std::string str;
const char *str;
switch (state) {
case State::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) {
using GatheringState = rtc::PeerConnection::GatheringState;
std::string str;
const char *str;
switch (state) {
case GatheringState::New:
str = "new";
break;
case GatheringState::InProgress:
str = "in_progress";
str = "in-progress";
break;
case GatheringState::Complete:
str = "complete";
@ -1003,3 +1066,29 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::Gathering
}
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;
}