Browse Source

Merge pull request #944 from paullouisageneau/ice-state

Add ICE state to API
Paul-Louis Ageneau 2 years ago
parent
commit
9b0f18cd0b

+ 13 - 0
include/rtc/peerconnection.hpp

@@ -46,6 +46,16 @@ public:
 		Closed = RTC_CLOSED
 	};
 
+	enum class IceState : int {
+		New = RTC_ICE_NEW,
+		Checking = RTC_ICE_CHECKING,
+		Connected = RTC_ICE_CONNECTED,
+		Completed = RTC_ICE_COMPLETED,
+		Failed = RTC_ICE_FAILED,
+		Disconnected = RTC_ICE_DISCONNECTED,
+		Closed = RTC_ICE_CLOSED
+	};
+
 	enum class GatheringState : int {
 		New = RTC_GATHERING_NEW,
 		InProgress = RTC_GATHERING_INPROGRESS,
@@ -68,6 +78,7 @@ public:
 
 	const Configuration *config() const;
 	State state() const;
+	IceState iceState() const;
 	GatheringState gatheringState() const;
 	SignalingState signalingState() const;
 	bool hasMedia() const;
@@ -94,6 +105,7 @@ public:
 	void onLocalDescription(std::function<void(Description description)> callback);
 	void onLocalCandidate(std::function<void(Candidate candidate)> callback);
 	void onStateChange(std::function<void(State state)> callback);
+	void onIceStateChange(std::function<void(IceState state)> callback);
 	void onGatheringStateChange(std::function<void(GatheringState state)> callback);
 	void onSignalingStateChange(std::function<void(SignalingState state)> callback);
 
@@ -109,6 +121,7 @@ public:
 } // namespace rtc
 
 RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state);
+RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::IceState state);
 RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out,
                                         rtc::PeerConnection::GatheringState state);
 RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out,

+ 12 - 0
include/rtc/rtc.h

@@ -67,6 +67,16 @@ typedef enum {
 	RTC_CLOSED = 5
 } rtcState;
 
+typedef enum {
+	RTC_ICE_NEW = 0,
+	RTC_ICE_CHECKING = 1,
+	RTC_ICE_CONNECTED = 2,
+	RTC_ICE_COMPLETED = 3,
+	RTC_ICE_FAILED = 4,
+	RTC_ICE_DISCONNECTED = 5,
+	RTC_ICE_CLOSED = 6
+} rtcIceState;
+
 typedef enum {
 	RTC_GATHERING_NEW = 0,
 	RTC_GATHERING_INPROGRESS = 1,
@@ -131,6 +141,7 @@ typedef void(RTC_API *rtcDescriptionCallbackFunc)(int pc, const char *sdp, const
 typedef void(RTC_API *rtcCandidateCallbackFunc)(int pc, const char *cand, const char *mid,
                                                 void *ptr);
 typedef void(RTC_API *rtcStateChangeCallbackFunc)(int pc, rtcState state, void *ptr);
+typedef void(RTC_API *rtcIceStateChangeCallbackFunc)(int pc, rtcIceState state, void *ptr);
 typedef void(RTC_API *rtcGatheringStateCallbackFunc)(int pc, rtcGatheringState state, void *ptr);
 typedef void(RTC_API *rtcSignalingStateCallbackFunc)(int pc, rtcSignalingState state, void *ptr);
 typedef void(RTC_API *rtcDataChannelCallbackFunc)(int pc, int dc, void *ptr);
@@ -179,6 +190,7 @@ RTC_C_EXPORT int rtcDeletePeerConnection(int pc);
 RTC_C_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb);
 RTC_C_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
 RTC_C_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
+RTC_C_EXPORT int rtcSetIceStateChangeCallback(int pc, rtcIceStateChangeCallbackFunc cb);
 RTC_C_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
 RTC_C_EXPORT int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb);
 

+ 14 - 0
src/capi.cpp

@@ -494,6 +494,20 @@ int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
 	});
 }
 
+int rtcSetIceStateChangeCallback(int pc, rtcIceStateChangeCallbackFunc cb) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onIceStateChange([pc, cb](PeerConnection::IceState state) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, static_cast<rtcIceState>(state), *ptr);
+			});
+		else
+			peerConnection->onIceStateChange(nullptr);
+		return RTC_ERR_SUCCESS;
+	});
+}
+
 int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) {
 	return wrap([&] {
 		auto peerConnection = getPeerConnection(pc);

+ 30 - 1
src/impl/peerconnection.cpp

@@ -152,16 +152,23 @@ shared_ptr<IceTransport> PeerConnection::initIceTransport() {
 				    return;
 			    switch (transportState) {
 			    case IceTransport::State::Connecting:
+				    changeIceState(IceState::Checking);
 				    changeState(State::Connecting);
 				    break;
 			    case IceTransport::State::Connected:
+				    changeIceState(IceState::Connected);
 				    initDtlsTransport();
 				    break;
+			    case IceTransport::State::Completed:
+				    changeIceState(IceState::Completed);
+				    break;
 			    case IceTransport::State::Failed:
+				    changeIceState(IceState::Failed);
 				    changeState(State::Failed);
 				    mProcessor.enqueue(&PeerConnection::remoteClose, shared_from_this());
 				    break;
 			    case IceTransport::State::Disconnected:
+				    changeIceState(IceState::Disconnected);
 				    changeState(State::Disconnected);
 				    mProcessor.enqueue(&PeerConnection::remoteClose, shared_from_this());
 				    break;
@@ -345,11 +352,14 @@ shared_ptr<SctpTransport> PeerConnection::getSctpTransport() const {
 void PeerConnection::closeTransports() {
 	PLOG_VERBOSE << "Closing transports";
 
+	// Change ICE state to sink state Closed
+	changeIceState(IceState::Closed);
+
 	// Change state to sink state Closed
 	if (!changeState(State::Closed))
 		return; // already closed
 
-	// Reset intercceptor and callbacks now that state is changed
+	// Reset interceptor and callbacks now that state is changed
 	setMediaHandler(nullptr);
 	resetCallbacks();
 
@@ -1175,6 +1185,24 @@ bool PeerConnection::changeState(State newState) {
 	return true;
 }
 
+bool PeerConnection::changeIceState(IceState newState) {
+	if (iceState.exchange(newState) == newState)
+		return false;
+
+	std::ostringstream s;
+	s << newState;
+	PLOG_INFO << "Changed ICE state to " << s.str();
+
+	if (newState == IceState::Closed) {
+		auto callback = std::move(iceStateChangeCallback); // steal the callback
+		callback(IceState::Closed);                        // call it synchronously
+	} else {
+		mProcessor.enqueue(&PeerConnection::trigger<IceState>, shared_from_this(),
+		                   &iceStateChangeCallback, newState);
+	}
+	return true;
+}
+
 bool PeerConnection::changeGatheringState(GatheringState newState) {
 	if (gatheringState.exchange(newState) == newState)
 		return false;
@@ -1207,6 +1235,7 @@ void PeerConnection::resetCallbacks() {
 	localDescriptionCallback = nullptr;
 	localCandidateCallback = nullptr;
 	stateChangeCallback = nullptr;
+	iceStateChangeCallback = nullptr;
 	gatheringStateChangeCallback = nullptr;
 	signalingStateChangeCallback = nullptr;
 	trackCallback = nullptr;

+ 4 - 0
src/impl/peerconnection.hpp

@@ -29,6 +29,7 @@ namespace rtc::impl {
 
 struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 	using State = rtc::PeerConnection::State;
+	using IceState = rtc::PeerConnection::IceState;
 	using GatheringState = rtc::PeerConnection::GatheringState;
 	using SignalingState = rtc::PeerConnection::SignalingState;
 
@@ -92,6 +93,7 @@ struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 	void flushPendingTracks();
 
 	bool changeState(State newState);
+	bool changeIceState(IceState newState);
 	bool changeGatheringState(GatheringState newState);
 	bool changeSignalingState(SignalingState newState);
 
@@ -108,6 +110,7 @@ struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 
 	const Configuration config;
 	std::atomic<State> state = State::New;
+	std::atomic<IceState> iceState = IceState::New;
 	std::atomic<GatheringState> gatheringState = GatheringState::New;
 	std::atomic<SignalingState> signalingState = SignalingState::Stable;
 	std::atomic<bool> negotiationNeeded = false;
@@ -118,6 +121,7 @@ struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 	synchronized_callback<Description> localDescriptionCallback;
 	synchronized_callback<Candidate> localCandidateCallback;
 	synchronized_callback<State> stateChangeCallback;
+	synchronized_callback<IceState> iceStateChangeCallback;
 	synchronized_callback<GatheringState> gatheringStateChangeCallback;
 	synchronized_callback<SignalingState> signalingStateChangeCallback;
 	synchronized_callback<shared_ptr<rtc::Track>> trackCallback;

+ 40 - 0
src/peerconnection.cpp

@@ -51,6 +51,10 @@ const Configuration *PeerConnection::config() const { return &impl()->config; }
 
 PeerConnection::State PeerConnection::state() const { return impl()->state; }
 
+PeerConnection::IceState PeerConnection::iceState() const {
+	return impl()->iceState;
+}
+
 PeerConnection::GatheringState PeerConnection::gatheringState() const {
 	return impl()->gatheringState;
 }
@@ -311,6 +315,10 @@ void PeerConnection::onStateChange(std::function<void(State state)> callback) {
 	impl()->stateChangeCallback = callback;
 }
 
+void PeerConnection::onIceStateChange(std::function<void(IceState state)> callback) {
+	impl()->iceStateChangeCallback = callback;
+}
+
 void PeerConnection::onGatheringStateChange(std::function<void(GatheringState state)> callback) {
 	impl()->gatheringStateChangeCallback = callback;
 }
@@ -377,6 +385,38 @@ std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state) {
 	return out << str;
 }
 
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::IceState state) {
+	using IceState = rtc::PeerConnection::IceState;
+	const char *str;
+	switch (state) {
+	case IceState::New:
+		str = "new";
+		break;
+	case IceState::Checking:
+		str = "checking";
+		break;
+	case IceState::Connected:
+		str = "connected";
+		break;
+	case IceState::Completed:
+		str = "completed";
+		break;
+	case IceState::Failed:
+		str = "failed";
+		break;
+	case IceState::Disconnected:
+		str = "disconnected";
+		break;
+	case IceState::Closed:
+		str = "closed";
+		break;
+	default:
+		str = "unknown";
+		break;
+	}
+	return out << str;
+}
+
 std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state) {
 	using GatheringState = rtc::PeerConnection::GatheringState;
 	const char *str;

+ 14 - 0
test/capi_connectivity.cpp

@@ -23,6 +23,7 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
 
 typedef struct {
 	rtcState state;
+	rtcIceState iceState;
 	rtcGatheringState gatheringState;
 	rtcSignalingState signalingState;
 	int pc;
@@ -53,6 +54,12 @@ static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
 	printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
 }
 
+static void RTC_API iceStateChangeCallback(int pc, rtcIceState state, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->iceState = state;
+	printf("ICE state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
+}
+
 static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	peer->gatheringState = state;
@@ -158,6 +165,7 @@ static Peer *createPeer(const rtcConfiguration *config) {
 	rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
 	rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
 	rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
+	rtcSetIceStateChangeCallback(peer->pc, iceStateChangeCallback);
 	rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
 	rtcSetSignalingStateChangeCallback(peer->pc, signalingStateCallback);
 
@@ -246,6 +254,12 @@ int test_capi_connectivity_main() {
 		goto error;
 	}
 
+	if ((peer1->iceState != RTC_ICE_CONNECTED && peer1->iceState != RTC_ICE_COMPLETED) ||
+	    (peer2->iceState != RTC_ICE_CONNECTED && peer2->iceState != RTC_ICE_COMPLETED)) {
+		fprintf(stderr, "PeerConnection is not connected\n");
+		goto error;
+	}
+
 	if (!peer1->connected || !peer2->connected) {
 		fprintf(stderr, "DataChannel is not connected\n");
 		goto error;

+ 15 - 1
test/connectivity.cpp

@@ -68,6 +68,10 @@ void test_connectivity(bool signal_wrong_fingerprint) {
 
 	pc1.onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
 
+	pc1.onIceStateChange([](PeerConnection::IceState state) {
+		cout << "ICE state 1: " << state << endl;
+	});
+
 	pc1.onGatheringStateChange([](PeerConnection::GatheringState state) {
 		cout << "Gathering state 1: " << state << endl;
 	});
@@ -88,6 +92,10 @@ void test_connectivity(bool signal_wrong_fingerprint) {
 
 	pc2.onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
 
+	pc2.onIceStateChange([](PeerConnection::IceState state) {
+		cout << "ICE state 2: " << state << endl;
+	});
+
 	pc2.onGatheringStateChange([](PeerConnection::GatheringState state) {
 		cout << "Gathering state 2: " << state << endl;
 	});
@@ -144,10 +152,16 @@ void test_connectivity(bool signal_wrong_fingerprint) {
 	while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
 		this_thread::sleep_for(1s);
 
-	if (pc1.state() != PeerConnection::State::Connected &&
+	if (pc1.state() != PeerConnection::State::Connected ||
 	    pc2.state() != PeerConnection::State::Connected)
 		throw runtime_error("PeerConnection is not connected");
 
+	if ((pc1.iceState() != PeerConnection::IceState::Connected &&
+	     pc1.iceState() != PeerConnection::IceState::Completed) ||
+	    (pc2.iceState() != PeerConnection::IceState::Connected &&
+	     pc2.iceState() != PeerConnection::IceState::Completed))
+		throw runtime_error("ICE is not connected");
+
 	if (!adc2 || !adc2->isOpen() || !dc1->isOpen())
 		throw runtime_error("DataChannel is not open");
 

+ 1 - 1
test/negotiated.cpp

@@ -64,7 +64,7 @@ void test_negotiated() {
 	while (!negotiated1->isOpen() || !negotiated2->isOpen() && attempts--)
 		this_thread::sleep_for(1s);
 
-	if (pc1.state() != PeerConnection::State::Connected &&
+	if (pc1.state() != PeerConnection::State::Connected ||
 	    pc2.state() != PeerConnection::State::Connected)
 		throw runtime_error("PeerConnection is not connected");
 

+ 1 - 1
test/track.cpp

@@ -104,7 +104,7 @@ void test_track() {
 	while ((!(at2 = std::atomic_load(&t2)) || !at2->isOpen() || !t1->isOpen()) && attempts--)
 		this_thread::sleep_for(1s);
 
-	if (pc1.state() != PeerConnection::State::Connected &&
+	if (pc1.state() != PeerConnection::State::Connected ||
 	    pc2.state() != PeerConnection::State::Connected)
 		throw runtime_error("PeerConnection is not connected");
 

+ 13 - 1
test/turn_connectivity.cpp

@@ -40,6 +40,9 @@ void test_turn_connectivity() {
 
 	pc1.onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
 
+	pc1.onIceStateChange(
+	    [](PeerConnection::IceState state) { cout << "ICE state 1: " << state << endl; });
+
 	pc1.onGatheringStateChange([&pc1, &pc2](PeerConnection::GatheringState state) {
 		cout << "Gathering state 1: " << state << endl;
 		if (state == PeerConnection::GatheringState::Complete) {
@@ -69,6 +72,9 @@ void test_turn_connectivity() {
 
 	pc2.onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
 
+	pc2.onIceStateChange(
+	    [](PeerConnection::IceState state) { cout << "ICE state 2: " << state << endl; });
+
 	pc2.onGatheringStateChange([](PeerConnection::GatheringState state) {
 		cout << "Gathering state 2: " << state << endl;
 	});
@@ -125,10 +131,16 @@ void test_turn_connectivity() {
 	while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
 		this_thread::sleep_for(1s);
 
-	if (pc1.state() != PeerConnection::State::Connected &&
+	if (pc1.state() != PeerConnection::State::Connected ||
 	    pc2.state() != PeerConnection::State::Connected)
 		throw runtime_error("PeerConnection is not connected");
 
+	if ((pc1.iceState() != PeerConnection::IceState::Connected &&
+	     pc1.iceState() != PeerConnection::IceState::Completed) ||
+	    (pc2.iceState() != PeerConnection::IceState::Connected &&
+	     pc2.iceState() != PeerConnection::IceState::Completed))
+		throw runtime_error("ICE is not connected");
+
 	if (!adc2 || !adc2->isOpen() || !dc1->isOpen())
 		throw runtime_error("DataChannel is not open");