--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -228,8 +228,12 @@ skip-if = toolkit == 'gonk' || buildapp
skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_remoteReofferRollback.html]
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
[test_selftest.html]
# Bug 1227781: Crash with bogus TURN server.
[test_peerConnection_bug1227781.html]
# Bug 950317: Hack for making a cleanup hook after finishing all WebRTC cases
[test_zmedia_cleanup.html]
+[test_peerConnection_restartIceNoBundle.html]
+[test_peerConnection_restartIce.html]
+[test_peerConnection_restartIceLocalRollback.html]
+[test_peerConnection_restartIceLocalAndRemoteRollback.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIce.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ function PC_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.iceCheckingRestartExpected = true;
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.iceCheckingRestartExpected = true;
+ }
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceLocalAndRemoteRollback.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, local and remote rollback"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ var firstNegotiationSize = test.chain.commands.length;
+
+ addRenegotiation(test.chain,
+ [
+ // causes a full, normal ice restart
+ function PC_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.iceCheckingRestartExpected = true;
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.iceCheckingRestartExpected = true;
+ },
+ ]
+ );
+
+ test.chain.replaceAfter('PC_REMOTE_CREATE_ANSWER',
+ [
+ function PC_LOCAL_SETUP_ICE_HANDLER(test) {
+ test.pcLocal.setupIceCandidateHandler(test);
+ if (test.testOptions.steeplechase) {
+ test.pcLocal.endOfTrickleIce.then(() => {
+ send_message({"type": "end_of_trickle_ice"});
+ });
+ }
+ },
+
+ function PC_REMOTE_ROLLBACK(test) {
+ return test.setRemoteDescription(
+ test.pcRemote,
+ new RTCSessionDescription({ type: "rollback" }),
+ STABLE);
+ },
+
+ function PC_LOCAL_ROLLBACK(test) {
+ // We haven't negotiated the new stream yet.
+ test.pcLocal.expectNegotiationNeeded();
+ return test.setLocalDescription(
+ test.pcLocal,
+ new RTCSessionDescription({ type: "rollback", sdp: ""}),
+ STABLE);
+ },
+
+ // Rolling back should shut down gathering
+ function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleIce;
+ },
+ ],
+ firstNegotiationSize // Second PC_REMOTE_SET_REMOTE_DESCRIPTION
+ );
+ test.chain.append(commandsPeerConnectionOfferAnswer);
+
+ // for now, only use one stream, because rollback doesn't seem to
+ // like multiple streams
+ test.setMediaConstraints([{audio: true}],
+ [{audio: true}]);
+ test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceLocalRollback.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, local rollback"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ // causes a full, normal ice restart
+ function PC_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.iceCheckingRestartExpected = true;
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.iceCheckingRestartExpected = true;
+ },
+
+ // causes an ice restart and then rolls it back
+ // (does not result in sending and offer)
+ function PC_LOCAL_SETUP_ICE_HANDLER(test) {
+ test.pcLocal.setupIceCandidateHandler(test);
+ if (test.testOptions.steeplechase) {
+ test.pcLocal.endOfTrickleIce.then(() => {
+ send_message({"type": "end_of_trickle_ice"});
+ });
+ }
+ },
+ function PC_LOCAL_CREATE_AND_SET_OFFER(test) {
+ return test.createOffer(test.pcLocal).then(offer => {
+ return test.setLocalDescription(test.pcLocal,
+ offer,
+ HAVE_LOCAL_OFFER);
+ });
+ },
+ function PC_LOCAL_ROLLBACK(test) {
+ return test.setLocalDescription(
+ test.pcLocal,
+ new RTCSessionDescription({ type: "rollback",
+ sdp: ""}),
+ STABLE);
+ },
+ // Rolling back should shut down gathering
+ function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleIce;
+ }
+ ]
+ );
+
+ // for now, only use one stream, because rollback doesn't seem to
+ // like multiple streams
+ test.setMediaConstraints([{audio: true}],
+ [{audio: true}]);
+ test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceNoBundle.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, no bundle"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ function PC_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.iceCheckingRestartExpected = true;
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.iceCheckingRestartExpected = true;
+ }
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/RTCPeerConnection.webidl
+++ b/dom/webidl/RTCPeerConnection.webidl
@@ -57,17 +57,17 @@ dictionary RTCOfferAnswerOptions {
};
dictionary RTCAnswerOptions : RTCOfferAnswerOptions {
};
dictionary RTCOfferOptions : RTCOfferAnswerOptions {
long offerToReceiveVideo;
long offerToReceiveAudio;
- // boolean iceRestart = false; // Not implemented (Bug 906986)
+ boolean iceRestart;
// Mozilla proprietary options (at risk: Bug 1196974)
boolean mozDontOfferDataChannel;
boolean mozBundleOnly;
// TODO: Remove old constraint-like RTCOptions support soon (Bug 1064223).
DeprecatedRTCOfferOptionsSet mandatory;
sequence<DeprecatedRTCOfferOptionsSet> _optional;
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -451,36 +451,86 @@ NrIceCtx::InitializeCryptoAndLogging(boo
if (force_net_interface.Length() > 0) {
// Stupid cast.... but needed
const nsCString& flat = PromiseFlatCString(static_cast<nsACString&>(force_net_interface));
NR_reg_set_string((char *)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME, const_cast<char*>(flat.get()));
}
}
}
+std::string
+NrIceCtx::GetNewUfrag()
+{
+ char* ufrag;
+ int r;
+
+ if ((r=nr_ice_get_new_ice_ufrag(&ufrag)))
+ return "";
+
+ std::string ufragStr = ufrag;
+ RFREE(ufrag);
+
+ return ufragStr;
+}
+
+std::string
+NrIceCtx::GetNewPwd()
+{
+ char* pwd;
+ int r;
+
+ if ((r=nr_ice_get_new_ice_pwd(&pwd)))
+ return "";
+
+ std::string pwdStr = pwd;
+ RFREE(pwd);
+
+ return pwdStr;
+}
+
bool
NrIceCtx::Initialize(NrIceCtx* ctx,
bool hide_non_default)
{
+ std::string ufrag = GetNewUfrag();
+ std::string pwd = GetNewPwd();
+
+ return Initialize(ctx,
+ hide_non_default,
+ ufrag,
+ pwd);
+}
+
+bool
+NrIceCtx::Initialize(NrIceCtx* ctx,
+ bool hide_non_default,
+ const std::string& ufrag,
+ const std::string& pwd)
+{
// Create the ICE context
int r;
UINT4 flags = ctx->offerer_ ? NR_ICE_CTX_FLAGS_OFFERER:
NR_ICE_CTX_FLAGS_ANSWERER;
flags |= NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION;
if (ctx->policy_ == ICE_POLICY_RELAY) {
flags |= NR_ICE_CTX_FLAGS_RELAY_ONLY;
}
if (hide_non_default)
flags |= NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS;
- r = nr_ice_ctx_create(const_cast<char *>(ctx->name_.c_str()),
+ r = nr_ice_ctx_create_with_credentials(const_cast<char *>(ctx->name_.c_str()),
flags,
+ const_cast<char *>(ufrag.c_str()),
+ const_cast<char *>(pwd.c_str()),
&ctx->ctx_);
+ MOZ_ASSERT(ufrag == ctx->ctx_->ufrag);
+ MOZ_ASSERT(pwd == ctx->ctx_->pwd);
+
if (r) {
MOZ_MTLOG(ML_ERROR, "Couldn't create ICE ctx for '" << ctx->name_ << "'");
return false;
}
nr_interface_prioritizer *prioritizer = CreateInterfacePrioritizer();
if (!prioritizer) {
MOZ_MTLOG(LogLevel::Error, "Couldn't create interface prioritizer.");
--- a/media/mtransport/nricectx.h
+++ b/media/mtransport/nricectx.h
@@ -215,16 +215,22 @@ class NrIceCtx {
};
static void InitializeCryptoAndLogging(bool allow_loopback,
bool tcp_enabled,
bool allow_link_local);
static bool Initialize(NrIceCtx* ice_ctx,
bool hide_non_default);
+ static std::string GetNewUfrag();
+ static std::string GetNewPwd();
+ static bool Initialize(NrIceCtx* ice_ctx,
+ bool hide_non_default,
+ const std::string& ufrag,
+ const std::string& pwd);
// Deinitialize all ICE global state. Used only for testing.
static void internal_DeinitializeGlobal();
nr_ice_ctx *ctx() { return ctx_; }
nr_ice_peer_ctx *peer() { return peer_; }
--- a/media/mtransport/nricectxhandler.cpp
+++ b/media/mtransport/nricectxhandler.cpp
@@ -40,13 +40,107 @@ NrIceCtxHandler::Create(const std::strin
return ctx;
}
RefPtr<NrIceMediaStream>
NrIceCtxHandler::CreateStream(const std::string& name, int components)
{
- return NrIceMediaStream::Create(this, name, components);
+ // To make tracking NrIceMediaStreams easier during ICE restart
+ // prepend an int to the name that increments with each ICE restart
+ std::ostringstream os;
+ os << restart_count << "-" << name;
+ return NrIceMediaStream::Create(this, os.str(), components);
+}
+
+
+void
+NrIceCtxHandler::SwapContextData(NrIceCtxHandler& ctx1, NrIceCtxHandler& ctx2)
+{
+ ctx1.streams_.swap(ctx2.streams_);
+
+ ConnectionState tmp_connection_state = ctx1.connection_state_;
+ GatheringState tmp_gathering_state = ctx1.gathering_state_;
+ nr_ice_ctx *tmp_ctx = ctx1.ctx_;
+ nr_ice_peer_ctx *tmp_peer_ctx = ctx1.peer_;
+ nr_ice_handler_vtbl *tmp_ice_handler_vtbl = ctx1.ice_handler_vtbl_;
+ nr_ice_handler *tmp_ice_handler = ctx1.ice_handler_;
+ bool tmp_trickle = ctx1.trickle_;
+ nsCOMPtr<nsIEventTarget> tmp_sts_target = ctx1.sts_target_;
+
+ ctx1.connection_state_ = ctx2.connection_state_;
+ ctx1.gathering_state_ = ctx2.gathering_state_;
+ ctx1.ctx_ = ctx2.ctx_;
+ ctx1.peer_ = ctx2.peer_;
+ ctx1.ice_handler_vtbl_ = ctx2.ice_handler_vtbl_;
+ ctx1.ice_handler_ = ctx2.ice_handler_;
+ if (ctx1.ice_handler_) {
+ ctx1.ice_handler_->obj = &ctx1;
+ }
+ ctx1.trickle_ = ctx2.trickle_;
+ ctx1.sts_target_ = ctx2.sts_target_;
+
+ ctx2.connection_state_ = tmp_connection_state;
+ ctx2.gathering_state_ = tmp_gathering_state;
+ ctx2.ctx_ = tmp_ctx;
+ ctx2.peer_ = tmp_peer_ctx;
+ ctx2.ice_handler_vtbl_ = tmp_ice_handler_vtbl;
+ ctx2.ice_handler_ = tmp_ice_handler;
+ if (ctx2.ice_handler_) {
+ ctx2.ice_handler_->obj = &ctx2;
+ }
+ ctx2.trickle_ = tmp_trickle;
+ ctx2.sts_target_ = tmp_sts_target;
+}
+
+
+void
+NrIceCtxHandler::BeginIceRestart()
+{
+ std::string ufrag = GetNewUfrag();
+ std::string pwd = GetNewPwd();
+ BeginIceRestart(ufrag, pwd);
+}
+
+void
+NrIceCtxHandler::BeginIceRestart(const std::string& ufrag,
+ const std::string& pwd)
+{
+ MOZ_ASSERT(!restart_ctx, "existing ice restart in progress");
+ RefPtr<NrIceCtxHandler> new_ctx = new NrIceCtxHandler(name_, offerer_, policy_);
+
+ // reconstitute the hide_non_default bool based on the flags stored in the
+ // original ctx flags
+ bool hide_non_default = ctx_->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS;
+
+ SwapContextData(*new_ctx, *this);
+
+ MOZ_ASSERT(NrIceCtx::Initialize(this,
+ hide_non_default,
+ ufrag,
+ pwd));
+
+ ++restart_count;
+ restart_ctx = new_ctx;
+}
+
+
+void
+NrIceCtxHandler::FinalizeIceRestart()
+{
+ // no harm calling this even if we're not in the middle of restarting
+ restart_ctx = nullptr;
+}
+
+
+void
+NrIceCtxHandler::RollbackIceRestart()
+{
+ if (restart_ctx == nullptr) {
+ return;
+ }
+ SwapContextData(*restart_ctx, *this);
+ restart_ctx = nullptr;
}
} // close namespace
--- a/media/mtransport/nricectxhandler.h
+++ b/media/mtransport/nricectxhandler.h
@@ -15,22 +15,35 @@ public:
bool allow_link_local = false,
bool hide_non_default = false,
Policy policy = ICE_POLICY_ALL);
// Create a media stream
RefPtr<NrIceMediaStream> CreateStream(const std::string& name,
int components);
+ void BeginIceRestart(); // used in testing
+ void BeginIceRestart(const std::string& ufrag,
+ const std::string& pwd);
+ bool IsRestarting() { return (restart_ctx != nullptr); }
+ void FinalizeIceRestart();
+ void RollbackIceRestart();
+
protected:
NrIceCtxHandler(const std::string& name,
bool offerer,
Policy policy)
- : NrIceCtx(name, offerer, policy)
- {}
+ : NrIceCtx(name, offerer, policy),
+ restart_ctx(nullptr), restart_count(0) {}
NrIceCtxHandler(); // disable
virtual ~NrIceCtxHandler();
+
+ void SwapContextData(NrIceCtxHandler& ctx1, NrIceCtxHandler& ctx2);
+
+private:
+ RefPtr<NrIceCtxHandler> restart_ctx; // for while restart is in progress
+ int restart_count; // used to differentiate streams between restarted ctx
};
} // close namespace
#endif // nricectxhandler_h__
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -564,16 +564,60 @@ class IceTestPeer : public sigslot::has_
WrapRunnableRet(&result, this, &IceTestPeer::is_ready_s, stream),
NS_DISPATCH_SYNC);
return result;
}
bool ice_complete() { return ice_complete_; }
bool ice_reached_checking() { return ice_reached_checking_; }
size_t received() { return received_; }
size_t sent() { return sent_; }
+ void resetStats() { received_ = sent_ = 0; }
+
+
+ void RestartIce() {
+ test_utils->sts_target()->Dispatch(
+ WrapRunnable(this, &IceTestPeer::RestartIce_s),
+ NS_DISPATCH_SYNC);
+ }
+
+
+ void RestartIce_s() {
+ ice_ctx_->BeginIceRestart();
+ // take care of some local bookkeeping
+ ready_ct_ = 0;
+ gathering_complete_ = false;
+ ice_complete_ = false;
+ ice_reached_checking_ = false;
+ remote_ = nullptr;
+ }
+
+
+ void FinalizeIceRestart() {
+ test_utils->sts_target()->Dispatch(
+ WrapRunnable(this, &IceTestPeer::FinalizeIceRestart_s),
+ NS_DISPATCH_SYNC);
+ }
+
+
+ void FinalizeIceRestart_s() {
+ ice_ctx_->FinalizeIceRestart();
+ }
+
+
+ void RollbackIceRestart() {
+ test_utils->sts_target()->Dispatch(
+ WrapRunnable(this, &IceTestPeer::RollbackIceRestart_s),
+ NS_DISPATCH_SYNC);
+ }
+
+
+ void RollbackIceRestart_s() {
+ ice_ctx_->RollbackIceRestart();
+ }
+
// Start connecting to another peer
void Connect_s(IceTestPeer *remote, TrickleMode trickle_mode,
bool start = true) {
nsresult res;
remote_ = remote;
@@ -2200,16 +2244,103 @@ TEST_F(IceConnectTest, TestGatherAutoPri
TEST_F(IceConnectTest, TestConnect) {
AddStream("first", 1);
ASSERT_TRUE(Gather());
Connect();
}
+
+TEST_F(IceConnectTest, TestConnectRestartIce) {
+ AddStream("first", 1);
+ ASSERT_TRUE(Gather());
+ Connect();
+ SendReceive(p1_, p2_);
+ p1_->resetStats();
+ p2_->resetStats();
+
+ p2_->RestartIce();
+ ASSERT_FALSE(p2_->gathering_complete());
+
+ // verify p1 and p2 streams are still connected after restarting ice on p2
+ SendReceive(p1_, p2_);
+ p1_->resetStats();
+ p2_->resetStats();
+
+ mozilla::ScopedDeletePtr<IceTestPeer> p3_;
+ p3_ = new IceTestPeer("P3", true, false, false, false, false);
+ InitPeer(p3_);
+ p3_->AddStream(1);
+
+ p2_->AddStream(1);
+ ASSERT_TRUE(GatherCallerAndCallee(p2_, p3_));
+ std::cout << "-------------------------------------------------" << std::endl;
+ ConnectCallerAndCallee(p2_, p3_);
+ SendReceive(p1_, p2_); // p1 and p2 still connected
+ p2_->resetStats();
+ SendReceive(p3_, p2_); // p3 and p2 are now connected
+
+ p2_->FinalizeIceRestart();
+ p1_->resetStats();
+ p2_->resetStats();
+ p3_->resetStats();
+ SendReceive(p3_, p2_); // p3 and p2 are still connected
+
+ p1_->resetStats();
+ p2_->resetStats();
+ SendReceive(p1_, p2_, false, true); // p1 and p2 not connected
+
+ p3_ = nullptr;
+}
+
+
+TEST_F(IceConnectTest, TestConnectRestartIceThenAbort) {
+ AddStream("first", 1);
+ ASSERT_TRUE(Gather());
+ Connect();
+ SendReceive(p1_, p2_);
+ p1_->resetStats();
+ p2_->resetStats();
+
+ p2_->RestartIce();
+ ASSERT_FALSE(p2_->gathering_complete());
+
+ // verify p1 and p2 streams are still connected after restarting ice on p2
+ SendReceive(p1_, p2_);
+ p1_->resetStats();
+ p2_->resetStats();
+
+ mozilla::ScopedDeletePtr<IceTestPeer> p3_;
+ p3_ = new IceTestPeer("P3", true, false, false, false, false);
+ InitPeer(p3_);
+ p3_->AddStream(1);
+
+ p2_->AddStream(1);
+ ASSERT_TRUE(GatherCallerAndCallee(p2_, p3_));
+ std::cout << "-------------------------------------------------" << std::endl;
+ ConnectCallerAndCallee(p2_, p3_);
+ SendReceive(p1_, p2_); // p1 and p2 still connected
+ p2_->resetStats();
+ SendReceive(p3_, p2_); // p3 and p2 are now connected
+
+ p2_->RollbackIceRestart();
+ p1_->resetStats();
+ p2_->resetStats();
+ p3_->resetStats();
+ SendReceive(p1_, p2_); // p1 and p2 are still connected
+
+ p3_->resetStats();
+ p2_->resetStats();
+ SendReceive(p3_, p2_, false, true); // p3 and p2 not connected
+
+ p3_ = nullptr;
+}
+
+
TEST_F(IceConnectTest, TestConnectTcp) {
Init(false, true);
AddStream("first", 1);
ASSERT_TRUE(Gather());
SetCandidateFilter(IsTcpCandidate);
SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
Connect();
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -314,38 +314,55 @@ int nr_ice_fetch_turn_servers(int ct, nr
if (_status) RFREE(servers);
return(_status);
}
#endif /* USE_TURN */
#define MAXADDRS 100 /* Ridiculously high */
int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp)
{
+ int r,_status;
+ char *ufrag = 0;
+ char *pwd = 0;
+
+ if (r=nr_ice_get_new_ice_ufrag(&ufrag))
+ ABORT(r);
+ if (r=nr_ice_get_new_ice_pwd(&pwd))
+ ABORT(r);
+
+ if (r=nr_ice_ctx_create_with_credentials(label, flags, ufrag, pwd, ctxp))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ RFREE(ufrag);
+ RFREE(pwd);
+
+ return(_status);
+ }
+
+int nr_ice_ctx_create_with_credentials(char *label, UINT4 flags, char *ufrag, char *pwd, nr_ice_ctx **ctxp)
+ {
nr_ice_ctx *ctx=0;
int r,_status;
- char buf[100];
if(r=r_log_register("ice", &LOG_ICE))
ABORT(r);
if(!(ctx=RCALLOC(sizeof(nr_ice_ctx))))
ABORT(R_NO_MEMORY);
ctx->flags=flags;
if(!(ctx->label=r_strdup(label)))
ABORT(R_NO_MEMORY);
- if(r=nr_ice_random_string(buf,8))
- ABORT(r);
- if(!(ctx->ufrag=r_strdup(buf)))
+ if(!(ctx->ufrag=r_strdup(ufrag)))
ABORT(r);
- if(r=nr_ice_random_string(buf,32))
- ABORT(r);
- if(!(ctx->pwd=r_strdup(buf)))
+ if(!(ctx->pwd=r_strdup(pwd)))
ABORT(r);
/* Get the STUN servers */
if(r=NR_reg_get_child_count(NR_ICE_REG_STUN_SRV_PRFX,
(unsigned int *)&ctx->stun_server_ct)||ctx->stun_server_ct==0) {
r_log(LOG_ICE,LOG_WARNING,"ICE(%s): No STUN servers specified", ctx->label);
ctx->stun_server_ct=0;
}
@@ -950,8 +967,45 @@ int nr_ice_ctx_hide_candidate(nr_ice_ctx
if (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) {
if (cand->type == HOST)
return 1;
}
return 0;
}
+
+int nr_ice_get_new_ice_ufrag(char** ufrag)
+ {
+ int r,_status;
+ char buf[10];
+
+ if(r=nr_ice_random_string(buf,8))
+ ABORT(r);
+ if(!(*ufrag=r_strdup(buf)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if(_status) {
+ RFREE(*ufrag);
+ }
+ return(_status);
+ }
+
+int nr_ice_get_new_ice_pwd(char** pwd)
+ {
+ int r,_status;
+ char buf[40];
+
+ if(r=nr_ice_random_string(buf,32))
+ ABORT(r);
+ if(!(*pwd=r_strdup(buf)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if(_status) {
+ RFREE(*pwd);
+ }
+ return(_status);
+ }
+
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
@@ -150,16 +150,17 @@ struct nr_ice_ctx_ {
nr_ice_trickle_candidate_cb trickle_cb;
void *trickle_cb_arg;
char force_net_interface[MAXIFNAME];
};
int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp);
+int nr_ice_ctx_create_with_credentials(char *label, UINT4 flags, char* ufrag, char* pwd, nr_ice_ctx **ctxp);
#define NR_ICE_CTX_FLAGS_OFFERER 1
#define NR_ICE_CTX_FLAGS_ANSWERER (1<<1)
#define NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION (1<<2)
#define NR_ICE_CTX_FLAGS_LITE (1<<3)
#define NR_ICE_CTX_FLAGS_RELAY_ONLY (1<<4)
#define NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS (1<<5)
int nr_ice_ctx_destroy(nr_ice_ctx **ctxp);
@@ -176,16 +177,18 @@ int nr_ice_ctx_finalize(nr_ice_ctx *ctx,
int nr_ice_ctx_set_stun_servers(nr_ice_ctx *ctx,nr_ice_stun_server *servers, int ct);
int nr_ice_ctx_set_turn_servers(nr_ice_ctx *ctx,nr_ice_turn_server *servers, int ct);
int nr_ice_ctx_set_resolver(nr_ice_ctx *ctx, nr_resolver *resolver);
int nr_ice_ctx_set_interface_prioritizer(nr_ice_ctx *ctx, nr_interface_prioritizer *prioritizer);
int nr_ice_ctx_set_turn_tcp_socket_wrapper(nr_ice_ctx *ctx, nr_socket_wrapper_factory *wrapper);
void nr_ice_ctx_set_socket_factory(nr_ice_ctx *ctx, nr_socket_factory *factory);
int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg);
int nr_ice_ctx_hide_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand);
+int nr_ice_get_new_ice_ufrag(char** ufrag);
+int nr_ice_get_new_ice_pwd(char** pwd);
#define NR_ICE_MAX_ATTRIBUTE_SIZE 256
extern int LOG_ICE;
#ifdef __cplusplus
}
#endif /* __cplusplus */
--- a/media/mtransport/transportlayerice.cpp
+++ b/media/mtransport/transportlayerice.cpp
@@ -80,44 +80,103 @@ namespace mozilla {
#ifdef ERROR
#undef ERROR
#endif
MOZ_MTLOG_MODULE("mtransport")
TransportLayerIce::TransportLayerIce(const std::string& name)
- : name_(name), ctx_(nullptr), stream_(nullptr), component_(0) {}
+ : name_(name),
+ ctx_(nullptr), stream_(nullptr), component_(0),
+ old_ctx_(nullptr), old_stream_(nullptr), old_component_(0)
+{
+ // setup happens later
+}
TransportLayerIce::~TransportLayerIce() {
// No need to do anything here, since we use smart pointers
}
void TransportLayerIce::SetParameters(RefPtr<NrIceCtx> ctx,
RefPtr<NrIceMediaStream> stream,
int component) {
+ // If SetParameters is called and we already have a stream_, this means
+ // we're handling an ICE restart. We need to hold the old stream until
+ // we know the new stream is working.
+ if (stream_ && !old_stream_) {
+ MOZ_ASSERT(stream_ != stream); // make sure we're getting a different stream
+ MOZ_ASSERT(old_ctx_ == nullptr);
+ MOZ_ASSERT(old_stream_ == nullptr);
+ MOZ_ASSERT(old_component_ == 0);
+ // Here we leave the old stream's signals connected until we don't need
+ // it anymore. They will be disconnected if ice restart is successful.
+ old_ctx_ = ctx_;
+ old_stream_ = stream_;
+ old_component_ = component_;
+ MOZ_MTLOG(ML_INFO, LAYER_INFO << "SetParameters save old stream("
+ << old_stream_->name() << ","
+ << old_component_ << ")");
+ }
+
ctx_ = ctx;
stream_ = stream;
component_ = component;
PostSetup();
}
void TransportLayerIce::PostSetup() {
target_ = ctx_->thread();
stream_->SignalReady.connect(this, &TransportLayerIce::IceReady);
stream_->SignalFailed.connect(this, &TransportLayerIce::IceFailed);
stream_->SignalPacketReceived.connect(this,
&TransportLayerIce::IcePacketReceived);
if (stream_->state() == NrIceMediaStream::ICE_OPEN) {
TL_SET_STATE(TS_OPEN);
+ // Reset old ice stream if new stream is good
+ ResetOldStream();
}
}
+void
+TransportLayerIce::ResetOldStream()
+{
+ if (old_stream_ == nullptr) {
+ return; // no work to do
+ }
+ // ICE Ready on the new stream, we can forget the old stream now
+ MOZ_MTLOG(ML_INFO, LAYER_INFO << "ResetOldStream(" << old_stream_->name() << ","
+ << old_component_ << ")");
+ old_stream_->SignalReady.disconnect(this);
+ old_stream_->SignalFailed.disconnect(this);
+ old_stream_->SignalPacketReceived.disconnect(this);
+ old_ctx_ = nullptr;
+ old_stream_ = nullptr;
+ old_component_ = 0;
+}
+
+void
+TransportLayerIce::RestoreOldStream()
+{
+ if (old_stream_ == nullptr) {
+ return; // no work to do
+ }
+ // ICE Failed on the new stream, we need to restore the old stream now
+ MOZ_MTLOG(ML_INFO, LAYER_INFO << "RestoreOldStream(" << old_stream_->name() << ","
+ << old_component_ << ")");
+ stream_->SignalReady.disconnect(this);
+ stream_->SignalFailed.disconnect(this);
+ stream_->SignalPacketReceived.disconnect(this);
+ ctx_ = old_ctx_;
+ stream_ = old_stream_;
+ component_ = old_component_;
+}
+
TransportResult TransportLayerIce::SendPacket(const unsigned char *data,
size_t len) {
CheckThread();
nsresult res = stream_->SendPacket(component_, data, len);
if (!NS_SUCCEEDED(res)) {
return (res == NS_BASE_STREAM_WOULD_BLOCK) ?
TE_WOULDBLOCK : TE_ERROR;
@@ -131,22 +190,32 @@ TransportResult TransportLayerIce::SendP
void TransportLayerIce::IceCandidate(NrIceMediaStream *stream,
const std::string&) {
// NO-OP for now
}
void TransportLayerIce::IceReady(NrIceMediaStream *stream) {
CheckThread();
+ MOZ_MTLOG(ML_INFO, LAYER_INFO << "ICE Ready(" << stream->name() << ","
+ << component_ << ")");
TL_SET_STATE(TS_OPEN);
+ // Reset old ice stream if new stream is good after ice restart
+ // Possible to get IceReady call for old stream after restart? ?mjf?
+ ResetOldStream();
}
void TransportLayerIce::IceFailed(NrIceMediaStream *stream) {
CheckThread();
+ MOZ_MTLOG(ML_INFO, LAYER_INFO << "ICE Failed(" << stream->name() << ","
+ << component_ << ")");
TL_SET_STATE(TS_ERROR);
+ // Restore old ice stream if new stream fails during ice restart
+ // Possible to get IceFailed call for old stream after restart? ?mjf?
+ RestoreOldStream();
}
void TransportLayerIce::IcePacketReceived(NrIceMediaStream *stream, int component,
const unsigned char *data, int len) {
CheckThread();
// We get packets for both components, so ignore the ones that aren't
// for us.
if (component_ != component)
--- a/media/mtransport/transportlayerice.h
+++ b/media/mtransport/transportlayerice.h
@@ -49,17 +49,25 @@ class TransportLayerIce : public Transpo
void IcePacketReceived(NrIceMediaStream *stream, int component,
const unsigned char *data, int len);
TRANSPORT_LAYER_ID("ice")
private:
DISALLOW_COPY_ASSIGN(TransportLayerIce);
void PostSetup();
+ void ResetOldStream(); // called after successful ice restart
+ void RestoreOldStream(); // called after unsuccessful ice restart
const std::string name_;
RefPtr<NrIceCtx> ctx_;
RefPtr<NrIceMediaStream> stream_;
int component_;
+
+ // used to hold the old ice ctx, stream, and component
+ // note: holding the old ctx and component are probably overkill (mjf)
+ RefPtr<NrIceCtx> old_ctx_;
+ RefPtr<NrIceMediaStream> old_stream_;
+ int old_component_;
};
} // close namespace
#endif
--- a/media/webrtc/signaling/src/jsep/JsepSession.h
+++ b/media/webrtc/signaling/src/jsep/JsepSession.h
@@ -39,16 +39,17 @@ enum JsepSdpType {
kJsepSdpRollback
};
struct JsepOAOptions {};
struct JsepOfferOptions : public JsepOAOptions {
Maybe<size_t> mOfferToReceiveAudio;
Maybe<size_t> mOfferToReceiveVideo;
Maybe<bool> mDontOfferDataChannel;
+ Maybe<bool> mIceRestart; // currently ignored by JsepSession
};
struct JsepAnswerOptions : public JsepOAOptions {};
enum JsepBundlePolicy {
kBundleBalanced,
kBundleMaxCompat,
kBundleMaxBundle
};
@@ -81,16 +82,17 @@ public:
return mNegotiations;
}
// Set up the ICE And DTLS data.
virtual nsresult SetIceCredentials(const std::string& ufrag,
const std::string& pwd) = 0;
virtual nsresult SetBundlePolicy(JsepBundlePolicy policy) = 0;
virtual bool RemoteIsIceLite() const = 0;
+ virtual bool RemoteIsIceRestarting() const = 0;
virtual std::vector<std::string> GetIceOptions() const = 0;
virtual nsresult AddDtlsFingerprint(const std::string& algorithm,
const std::vector<uint8_t>& value) = 0;
virtual nsresult AddAudioRtpExtension(const std::string& extensionName) = 0;
virtual nsresult AddVideoRtpExtension(const std::string& extensionName) = 0;
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -148,16 +148,20 @@ JsepSessionImpl::RemoveTrack(const std::
nsresult
JsepSessionImpl::SetIceCredentials(const std::string& ufrag,
const std::string& pwd)
{
mLastError.clear();
mIceUfrag = ufrag;
mIcePwd = pwd;
+ if (mCurrentLocalDescription.get()) {
+ mLocalIsIceRestarting = true;
+ }
+
return NS_OK;
}
nsresult
JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy)
{
mLastError.clear();
if (mCurrentLocalDescription) {
@@ -635,28 +639,28 @@ JsepSessionImpl::CreateOffer(const JsepO
JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
return NS_ERROR_UNEXPECTED;
}
// Undo track assignments from a previous call to CreateOffer
// (ie; if the track has not been negotiated yet, it doesn't necessarily need
// to stay in the same m-section that it was in)
for (JsepSendingTrack& trackWrapper : mLocalTracks) {
- if (!trackWrapper.mTrack->GetNegotiatedDetails()) {
+ if (!trackWrapper.mTrack->GetNegotiatedDetails() || mLocalIsIceRestarting) {
trackWrapper.mAssignedMLine.reset();
}
}
UniquePtr<Sdp> sdp;
// Make the basic SDP that is common to offer/answer.
nsresult rv = CreateGenericSDP(&sdp);
NS_ENSURE_SUCCESS(rv, rv);
- if (mCurrentLocalDescription) {
+ if (mCurrentLocalDescription && !mLocalIsIceRestarting) {
rv = AddReofferMsections(*mCurrentLocalDescription,
*GetAnswer(),
sdp.get());
NS_ENSURE_SUCCESS(rv, rv);
}
// Ensure that we have all the m-sections we need, and disable extras
rv = SetupOfferMSections(options, sdp.get());
@@ -1203,16 +1207,40 @@ JsepSessionImpl::SetRemoteDescription(Js
NS_ENSURE_SUCCESS(rv, rv);
rv = ValidateRemoteDescription(*parsed);
NS_ENSURE_SUCCESS(rv, rv);
bool iceLite =
parsed->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
+ // check for mismatch ufrag/pwd indicating ice restart
+ bool iceRestarting = false;
+ if (mCurrentRemoteDescription.get()) {
+ for (size_t i = 0;
+ !iceRestarting && i < mCurrentRemoteDescription->GetMediaSectionCount();
+ ++i) {
+ if (mSdpHelper.MsectionIsDisabled(parsed->GetMediaSection(i)) ||
+ mSdpHelper.MsectionIsDisabled(mCurrentRemoteDescription->GetMediaSection(i))) {
+ continue;
+ }
+
+ const SdpAttributeList& newAttrs(
+ parsed->GetMediaSection(i).GetAttributeList());
+ const SdpAttributeList& oldAttrs(
+ mCurrentRemoteDescription->GetMediaSection(i).GetAttributeList());
+
+ if ((newAttrs.GetIceUfrag() != oldAttrs.GetIceUfrag()) ||
+ (newAttrs.GetIcePwd() != oldAttrs.GetIcePwd())) {
+ MOZ_MTLOG(ML_DEBUG, "Mismatched ice creds indicate ICE restart");
+ iceRestarting = true;
+ }
+ }
+ }
+
std::vector<std::string> iceOptions;
if (parsed->GetAttributeList().HasAttribute(
SdpAttribute::kIceOptionsAttribute)) {
iceOptions = parsed->GetAttributeList().GetIceOptions().mValues;
}
switch (type) {
case kJsepSdpOffer:
@@ -1224,16 +1252,17 @@ JsepSessionImpl::SetRemoteDescription(Js
break;
case kJsepSdpRollback:
MOZ_CRASH(); // Handled above
}
if (NS_SUCCEEDED(rv)) {
mRemoteIsIceLite = iceLite;
mIceOptions = iceOptions;
+ mRemoteIsIceRestarting = iceRestarting;
}
return rv;
}
nsresult
JsepSessionImpl::HandleNegotiatedSession(const UniquePtr<Sdp>& local,
const UniquePtr<Sdp>& remote)
@@ -1520,17 +1549,19 @@ JsepSessionImpl::AddTransportAttributes(
nsresult
JsepSessionImpl::CopyPreviousTransportParams(const Sdp& oldAnswer,
const Sdp& newOffer,
Sdp* newLocal)
{
for (size_t i = 0; i < oldAnswer.GetMediaSectionCount(); ++i) {
if (!mSdpHelper.MsectionIsDisabled(newLocal->GetMediaSection(i)) &&
- mSdpHelper.AreOldTransportParamsValid(oldAnswer, newOffer, i)) {
+ mSdpHelper.AreOldTransportParamsValid(oldAnswer, newOffer, i) &&
+ !(mLocalIsIceRestarting || mRemoteIsIceRestarting)
+ ) {
// If newLocal is an offer, this will be the number of components we used
// last time, and if it is an answer, this will be the number of
// components we've decided we're using now.
size_t numComponents = mTransports[i]->mComponents;
nsresult rv = mSdpHelper.CopyTransportParams(
numComponents,
mCurrentLocalDescription->GetMediaSection(i),
&newLocal->GetMediaSection(i));
@@ -1836,42 +1867,16 @@ JsepSessionImpl::ValidateRemoteDescripti
SdpHelper::BundledMids bundledMids;
nsresult rv = GetNegotiatedBundledMids(&bundledMids);
NS_ENSURE_SUCCESS(rv, rv);
SdpHelper::BundledMids newBundledMids;
rv = mSdpHelper.GetBundledMids(description, &newBundledMids);
NS_ENSURE_SUCCESS(rv, rv);
- for (size_t i = 0;
- i < mCurrentRemoteDescription->GetMediaSectionCount();
- ++i) {
- if (mSdpHelper.MsectionIsDisabled(description.GetMediaSection(i)) ||
- mSdpHelper.MsectionIsDisabled(mCurrentRemoteDescription->GetMediaSection(i))) {
- continue;
- }
-
- const SdpAttributeList& newAttrs(
- description.GetMediaSection(i).GetAttributeList());
- const SdpAttributeList& oldAttrs(
- mCurrentRemoteDescription->GetMediaSection(i).GetAttributeList());
-
- if ((newAttrs.GetIceUfrag() != oldAttrs.GetIceUfrag()) ||
- (newAttrs.GetIcePwd() != oldAttrs.GetIcePwd())) {
- JSEP_SET_ERROR("ICE restart is unsupported at this time "
- "(new remote description changes either the ice-ufrag "
- "or ice-pwd)" <<
- "ice-ufrag (old): " << oldAttrs.GetIceUfrag() <<
- "ice-ufrag (new): " << newAttrs.GetIceUfrag() <<
- "ice-pwd (old): " << oldAttrs.GetIcePwd() <<
- "ice-pwd (new): " << newAttrs.GetIcePwd());
- return NS_ERROR_INVALID_ARG;
- }
- }
-
return NS_OK;
}
nsresult
JsepSessionImpl::ValidateAnswer(const Sdp& offer, const Sdp& answer)
{
if (offer.GetMediaSectionCount() != answer.GetMediaSectionCount()) {
JSEP_SET_ERROR("Offer and answer have different number of m-lines "
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -30,16 +30,18 @@ class JsepSessionImpl : public JsepSessi
{
public:
JsepSessionImpl(const std::string& name, UniquePtr<JsepUuidGenerator> uuidgen)
: JsepSession(name),
mIsOfferer(false),
mWasOffererLastTime(false),
mIceControlling(false),
mRemoteIsIceLite(false),
+ mLocalIsIceRestarting(false),
+ mRemoteIsIceRestarting(false),
mBundlePolicy(kBundleBalanced),
mSessionId(0),
mSessionVersion(0),
mUuidGen(Move(uuidgen)),
mSdpHelper(&mLastError)
{
}
@@ -55,16 +57,22 @@ public:
const std::string& pwd) override;
nsresult SetBundlePolicy(JsepBundlePolicy policy) override;
virtual bool
RemoteIsIceLite() const override
{
return mRemoteIsIceLite;
}
+
+ virtual bool
+ RemoteIsIceRestarting() const override
+ {
+ return mRemoteIsIceRestarting;
+ }
virtual std::vector<std::string>
GetIceOptions() const override
{
return mIceOptions;
}
virtual nsresult AddDtlsFingerprint(const std::string& algorithm,
@@ -297,16 +305,18 @@ private:
std::vector<JsepTrackPair> mNegotiatedTrackPairs;
bool mIsOfferer;
bool mWasOffererLastTime;
bool mIceControlling;
std::string mIceUfrag;
std::string mIcePwd;
bool mRemoteIsIceLite;
+ bool mLocalIsIceRestarting;
+ bool mRemoteIsIceRestarting;
std::vector<std::string> mIceOptions;
JsepBundlePolicy mBundlePolicy;
std::vector<JsepDtlsFingerprint> mDtlsFingerprints;
uint64_t mSessionId;
uint64_t mSessionVersion;
std::vector<SdpExtmapAttributeList::Extmap> mAudioRtpExtensions;
std::vector<SdpExtmapAttributeList::Extmap> mVideoRtpExtensions;
UniquePtr<JsepUuidGenerator> mUuidGen;
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -194,28 +194,56 @@ FinalizeTransportFlow_s(RefPtr<PeerConne
for (auto i = aLayerList->values.begin(); i != aLayerList->values.end();
++i) {
layerQueue->push(*i);
}
aLayerList->values.clear();
(void)aFlow->PushLayers(layerQueue); // TODO(bug 854518): Process errors.
}
+static void
+AddNewIceStreamForRestart_s(RefPtr<PeerConnectionMedia> aPCMedia,
+ RefPtr<TransportFlow> aFlow,
+ size_t aLevel,
+ bool aIsRtcp)
+{
+ TransportLayerIce* ice =
+ static_cast<TransportLayerIce*>(aFlow->GetLayer("ice"));
+ ice->SetParameters(aPCMedia->ice_ctx(),
+ aPCMedia->ice_media_stream(aLevel),
+ aIsRtcp ? 2 : 1);
+}
+
nsresult
MediaPipelineFactory::CreateOrGetTransportFlow(
size_t aLevel,
bool aIsRtcp,
const JsepTransport& aTransport,
RefPtr<TransportFlow>* aFlowOutparam)
{
nsresult rv;
RefPtr<TransportFlow> flow;
flow = mPCMedia->GetTransportFlow(aLevel, aIsRtcp);
if (flow) {
+ if (mPCMedia->ice_ctx()->IsRestarting()) {
+ MOZ_MTLOG(ML_INFO, "Flow[" << flow->id() << "]: "
+ << "detected ICE restart - level: "
+ << aLevel << " rtcp: " << aIsRtcp);
+
+ rv = mPCMedia->GetSTSThread()->Dispatch(
+ WrapRunnableNM(AddNewIceStreamForRestart_s,
+ mPCMedia, flow, aLevel, aIsRtcp),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Failed to dispatch AddNewIceStreamForRestart_s");
+ return rv;
+ }
+ }
+
*aFlowOutparam = flow;
return NS_OK;
}
std::ostringstream osId;
osId << mPC->GetHandle() << ":" << aLevel << ","
<< (aIsRtcp ? "rtcp" : "rtp");
flow = new TransportFlow(osId.str());
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1481,26 +1481,32 @@ PeerConnectionImpl::NotifyDataChannel(al
#endif
}
NS_IMETHODIMP
PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions)
{
JsepOfferOptions options;
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // convert the RTCOfferOptions to JsepOfferOptions
if (aOptions.mOfferToReceiveAudio.WasPassed()) {
options.mOfferToReceiveAudio =
mozilla::Some(size_t(aOptions.mOfferToReceiveAudio.Value()));
}
if (aOptions.mOfferToReceiveVideo.WasPassed()) {
options.mOfferToReceiveVideo =
mozilla::Some(size_t(aOptions.mOfferToReceiveVideo.Value()));
}
+ if (aOptions.mIceRestart.WasPassed()) {
+ options.mIceRestart =
+ mozilla::Some(aOptions.mIceRestart.Value());
+ }
+
if (aOptions.mMozDontOfferDataChannel.WasPassed()) {
options.mDontOfferDataChannel =
mozilla::Some(aOptions.mMozDontOfferDataChannel.Value());
}
#endif
return CreateOffer(options);
}
@@ -1533,17 +1539,34 @@ PeerConnectionImpl::CreateOffer(const Js
PeerConnectionCtx::GetInstance()->queueJSEPOperation(
WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions));
STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)");
return NS_OK;
}
CSFLogDebug(logTag, "CreateOffer()");
- nsresult nrv = ConfigureJsepSessionCodecs();
+ nsresult nrv;
+ if (aOptions.mIceRestart.isSome() && *(aOptions.mIceRestart)) {
+ CSFLogInfo(logTag, "Offerer restarting ice");
+ std::string ufrag = mMedia->ice_ctx()->GetNewUfrag();
+ std::string pwd = mMedia->ice_ctx()->GetNewPwd();
+
+ mMedia->BeginIceRestart(ufrag, pwd);
+
+ nrv = mJsepSession->SetIceCredentials(ufrag, pwd);
+ if (NS_FAILED(nrv)) {
+ CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(nrv));
+ return nrv;
+ }
+ }
+
+ nrv = ConfigureJsepSessionCodecs();
if (NS_FAILED(nrv)) {
CSFLogError(logTag, "Failed to configure codecs");
return nrv;
}
STAMP_TIMECARD(mTimeCard, "Create Offer");
std::string offer;
@@ -1578,23 +1601,41 @@ PeerConnectionImpl::CreateAnswer()
PC_AUTO_ENTER_API_CALL(true);
RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
if (!pco) {
return NS_OK;
}
CSFLogDebug(logTag, "CreateAnswer()");
+
+ nsresult nrv;
+ if (mJsepSession->RemoteIsIceRestarting()) {
+ CSFLogInfo(logTag, "Answerer restarting ice");
+ std::string ufrag = mMedia->ice_ctx()->GetNewUfrag();
+ std::string pwd = mMedia->ice_ctx()->GetNewPwd();
+
+ mMedia->BeginIceRestart(ufrag, pwd);
+
+ nrv = mJsepSession->SetIceCredentials(ufrag, pwd);
+ if (NS_FAILED(nrv)) {
+ CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(nrv));
+ return nrv;
+ }
+ }
+
STAMP_TIMECARD(mTimeCard, "Create Answer");
// TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to
// add it as a param to CreateAnswer, and convert it here.
JsepAnswerOptions options;
std::string answer;
- nsresult nrv = mJsepSession->CreateAnswer(options, &answer);
+ nrv = mJsepSession->CreateAnswer(options, &answer);
JSErrorResult rv;
if (NS_FAILED(nrv)) {
Error error;
switch (nrv) {
case NS_ERROR_UNEXPECTED:
error = kInvalidState;
break;
default:
@@ -2734,16 +2775,20 @@ PeerConnectionImpl::SetSignalingState_m(
!rollback)) {
mMedia->EnsureTransports(*mJsepSession);
}
mSignalingState = aSignalingState;
bool fireNegotiationNeeded = false;
if (mSignalingState == PCImplSignalingState::SignalingStable) {
+ if (rollback && mMedia->ice_ctx()->IsRestarting()) {
+ mMedia->RollbackIceRestart();
+ }
+
// Either negotiation is done, or we've rolled back. In either case, we
// need to re-evaluate whether further negotiation is required.
mNegotiationNeeded = false;
// If we're rolling back a local offer, we might need to remove some
// transports, but nothing further needs to be done.
mMedia->ActivateOrRemoveTransports(*mJsepSession);
if (!rollback) {
mMedia->UpdateMediaPipelines(*mJsepSession);
@@ -3025,16 +3070,23 @@ void PeerConnectionImpl::IceConnectionSt
Telemetry::WEBRTC_ICE_ADD_CANDIDATE_ERRORS_GIVEN_FAILURE,
mAddCandidateErrorCount);
}
}
#endif
mIceConnectionState = domState;
+ if (mIceConnectionState == PCImplIceConnectionState::Connected ||
+ mIceConnectionState == PCImplIceConnectionState::Completed) {
+ if (mMedia->ice_ctx()->IsRestarting()) {
+ mMedia->FinalizeIceRestart();
+ }
+ }
+
// Would be nice if we had a means of converting one of these dom enums
// to a string that wasn't almost as much text as this switch statement...
switch (mIceConnectionState) {
case PCImplIceConnectionState::New:
STAMP_TIMECARD(mTimeCard, "Ice state: new");
break;
case PCImplIceConnectionState::Checking:
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -593,16 +593,80 @@ PeerConnectionMedia::StartIceChecks_s(
mIceCtx->SetControlling(aIsControlling ?
NrIceCtx::ICE_CONTROLLING :
NrIceCtx::ICE_CONTROLLED);
mIceCtx->StartChecks();
}
void
+PeerConnectionMedia::BeginIceRestart(const std::string& ufrag,
+ const std::string& pwd)
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ RUN_ON_THREAD(GetSTSThread(),
+ WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::BeginIceRestart_s,
+ std::string(ufrag), // Make copies.
+ std::string(pwd)),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::BeginIceRestart_s(const std::string& ufrag,
+ const std::string& pwd)
+{
+ ASSERT_ON_THREAD(mSTSThread);
+
+ ice_ctx()->BeginIceRestart(ufrag, pwd);
+}
+
+void
+PeerConnectionMedia::FinalizeIceRestart()
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ RUN_ON_THREAD(GetSTSThread(),
+ WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::FinalizeIceRestart_s),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::FinalizeIceRestart_s()
+{
+ ASSERT_ON_THREAD(mSTSThread);
+
+ ice_ctx()->FinalizeIceRestart();
+}
+
+void
+PeerConnectionMedia::RollbackIceRestart()
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ RUN_ON_THREAD(GetSTSThread(),
+ WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::RollbackIceRestart_s),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::RollbackIceRestart_s()
+{
+ ASSERT_ON_THREAD(mSTSThread);
+
+ ice_ctx()->RollbackIceRestart();
+}
+
+void
PeerConnectionMedia::AddIceCandidate(const std::string& candidate,
const std::string& mid,
uint32_t aMLine) {
RUN_ON_THREAD(GetSTSThread(),
WrapRunnable(
RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::AddIceCandidate_s,
std::string(candidate), // Make copies.
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -248,16 +248,24 @@ class PeerConnectionMedia : public sigsl
// Activate or remove ICE transports at the conclusion of offer/answer,
// or when rollback occurs.
void ActivateOrRemoveTransports(const JsepSession& aSession);
// Start ICE checks.
void StartIceChecks(const JsepSession& session);
+ // Begin ICE restart
+ void BeginIceRestart(const std::string& ufrag,
+ const std::string& pwd);
+ // Finalize ICE restart
+ void FinalizeIceRestart();
+ // Abort ICE restart
+ void RollbackIceRestart();
+
// Process a trickle ICE candidate.
void AddIceCandidate(const std::string& candidate, const std::string& mid,
uint32_t aMLine);
// Handle complete media pipelines.
nsresult UpdateMediaPipelines(const JsepSession& session);
// Add a track (main thread only)
@@ -437,16 +445,21 @@ class PeerConnectionMedia : public sigsl
void GatherIfReady();
void FlushIceCtxOperationQueueIfReady();
void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable);
void EnsureIceGathering_s();
void StartIceChecks_s(bool aIsControlling,
bool aIsIceLite,
const std::vector<std::string>& aIceOptionsList);
+ void BeginIceRestart_s(const std::string& ufrag,
+ const std::string& pwd);
+ void FinalizeIceRestart_s();
+ void RollbackIceRestart_s();
+
// Process a trickle ICE candidate.
void AddIceCandidate_s(const std::string& aCandidate, const std::string& aMid,
uint32_t aMLine);
// ICE events
void IceGatheringStateChange_s(NrIceCtx* ctx,
NrIceCtx::GatheringState state);
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -83,16 +83,21 @@ class OfferOptions : public mozilla::Jse
public:
void setInt32Option(const char *namePtr, size_t value) {
if (!strcmp(namePtr, "OfferToReceiveAudio")) {
mOfferToReceiveAudio = mozilla::Some(value);
} else if (!strcmp(namePtr, "OfferToReceiveVideo")) {
mOfferToReceiveVideo = mozilla::Some(value);
}
}
+ void setBoolOption(const char* namePtr, bool value) {
+ if (!strcmp(namePtr, "IceRestart")) {
+ mIceRestart = mozilla::Some(value);
+ }
+ }
private:
};
using namespace mozilla;
using namespace mozilla::dom;
// XXX Workaround for bug 998092 to maintain the existing broken semantics
template<>
@@ -529,17 +534,17 @@ class ParsedSDP {
}
}
void DeleteLine(const std::string &objType)
{
DeleteLines(objType, 1);
}
- // Replaces the first instance of objType in the SDP with
+ // Replaces the index-th instance of objType in the SDP with
// a new string.
// If content is an empty string then the line will be removed
void ReplaceLine(const std::string &objType,
const std::string &content,
size_t index = 0)
{
auto it = FindLine(objType, index);
if(it != sdp_lines_.end()) {
@@ -2529,22 +2534,16 @@ TEST_P(SignalingTest, RenegotiationAnswe
// ANSWER_AUDIO causes a new audio track to be added
OfferAnswer(options, ANSWER_AUDIO);
CloseStreams();
}
TEST_P(SignalingTest, BundleRenegotiation)
{
- if (UseBundle()) {
- // We don't support ICE restart, which is a prereq for renegotiating bundle
- // off.
- return;
- }
-
OfferOptions options;
OfferAnswer(options, OFFER_AV | ANSWER_AV);
// If we did bundle before, turn it off, if not, turn it on
if (a1_->mBundleEnabled && a2_->mBundleEnabled) {
a1_->SetBundleEnabled(false);
} else {
a1_->SetBundleEnabled(true);
@@ -3438,16 +3437,27 @@ TEST_P(SignalingTest, AudioOnlyG722Rejec
ASSERT_EQ(a2_->getLocalDescription().find("a=rtpmap:9 G722/8000"), std::string::npos);
CheckPipelines();
CheckStreams();
CloseStreams();
}
+TEST_P(SignalingTest, RestartIce)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ options.setBoolOption("IceRestart", true);
+ OfferAnswer(options, OFFER_NONE);
+
+ CloseStreams();
+}
+
TEST_P(SignalingTest, FullCallAudioNoMuxVideoMux)
{
if (UseBundle()) {
// This test doesn't make sense for bundle
return;
}
EnsureInit();