Skip to content

Commit e345484

Browse files
author
Antonis
committed
Fixed #684: Consider enforcing policy for ICE candidate timeouts
1 parent e5e2576 commit e345484

File tree

8 files changed

+169
-16
lines changed

8 files changed

+169
-16
lines changed

Examples/restcomm-olympus/app/src/main/java/org/restcomm/android/olympus/CallActivity.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,10 @@ private void handleCall(Intent intent) {
325325
connectParams.put(RCConnection.ParameterKeys.CONNECTION_PREFERRED_VIDEO_FRAME_RATE,
326326
frameRateString2Enum(prefs.getString(RCConnection.ParameterKeys.CONNECTION_PREFERRED_VIDEO_FRAME_RATE, ""))
327327
);
328+
// Needed until we implement Trickle ICE
329+
connectParams.put(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT,
330+
Integer.parseInt(prefs.getString(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT, "0"))
331+
);
328332

329333
// Here's how to set manually
330334
//connectParams.put(RCConnection.ParameterKeys.CONNECTION_PREFERRED_VIDEO_CODEC, RCConnection.VideoCodec.VIDEO_CODEC_VP8);
@@ -389,6 +393,11 @@ private void handleCall(Intent intent) {
389393
acceptParams.put(RCConnection.ParameterKeys.CONNECTION_PREFERRED_AUDIO_CODEC,
390394
audioCodecString2Enum(prefs.getString(RCConnection.ParameterKeys.CONNECTION_PREFERRED_AUDIO_CODEC, ""))
391395
);
396+
// Needed until we implement Trickle ICE
397+
acceptParams.put(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT,
398+
Integer.parseInt(prefs.getString(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT, "0"))
399+
);
400+
392401

393402
if (intent.getAction().equals(RCDevice.ACTION_INCOMING_CALL_ANSWER_VIDEO)) {
394403
acceptParams.put(RCConnection.ParameterKeys.CONNECTION_PREFERRED_VIDEO_CODEC,
@@ -495,6 +504,10 @@ public void onClick(View view) {
495504
acceptParams.put(RCConnection.ParameterKeys.CONNECTION_PREFERRED_VIDEO_FRAME_RATE,
496505
frameRateString2Enum(prefs.getString(RCConnection.ParameterKeys.CONNECTION_PREFERRED_VIDEO_FRAME_RATE, ""))
497506
);
507+
acceptParams.put(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT,
508+
Integer.parseInt(prefs.getString(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT, "0"))
509+
);
510+
498511

499512
// Check permissions asynchronously and then accept the call
500513
handlePermissions(true);
@@ -513,6 +526,10 @@ public void onClick(View view) {
513526
audioCodecString2Enum(prefs.getString(RCConnection.ParameterKeys.CONNECTION_PREFERRED_AUDIO_CODEC, ""))
514527
);
515528

529+
acceptParams.put(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT,
530+
Integer.parseInt(prefs.getString(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT, "0"))
531+
);
532+
516533
// Check permissions asynchronously and then accept the call
517534
handlePermissions(false);
518535
}

Examples/restcomm-olympus/app/src/main/java/org/restcomm/android/olympus/SettingsActivity.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ protected void onResume()
108108
int iceServersDiscoveryType = Integer.parseInt(prefs.getString(RCDevice.ParameterKeys.MEDIA_ICE_SERVERS_DISCOVERY_TYPE, "0"));
109109
updatedPref.setSummary(getResources().getStringArray(R.array.ice_servers_discovery_types_entries)[iceServersDiscoveryType]);
110110

111+
updatedPref = settingsFragment.findPreference(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT);
112+
//int candidateTimeout = Integer.parseInt(prefs.getString(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT, "0"));
113+
//updatedPref.setSummary(getResources().getStringArray(R.array.candidate_timeout_entries)[candidateTimeout]);
114+
updatedPref.setSummary(candidateTimeoutValue2Summary(prefs.getString(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT, "0")));
115+
111116
updated = false;
112117
}
113118

@@ -187,6 +192,15 @@ public boolean onOptionsItemSelected(MenuItem item)
187192
RCDevice.MediaIceServersDiscoveryType.values()[Integer.parseInt(iceServersDiscoveryType)]
188193
);
189194

195+
// Same for candidate timeout
196+
String candidateTimeout = "0";
197+
if (prefHashMap.containsKey(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT)) {
198+
candidateTimeout = (String) prefHashMap.get(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT);
199+
prefHashMap.remove(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT);
200+
}
201+
prefHashMap.put(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT,
202+
Integer.parseInt(candidateTimeout)
203+
);
190204

191205
RCUtils.validateSettingsParms(prefHashMap);
192206
if (!device.updateParams(params)) {
@@ -272,6 +286,17 @@ else if (key.equals(RCDevice.ParameterKeys.MEDIA_ICE_SERVERS_DISCOVERY_TYPE)) {
272286
}
273287
updated = true;
274288
}
289+
else if (key.equals(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT)) {
290+
params.put(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT,
291+
Integer.parseInt(prefs.getString(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT, "0")));
292+
Preference updatedPref = settingsFragment.findPreference(key);
293+
if (updatedPref != null) {
294+
//int candidateTimeout = Integer.parseInt(prefs.getString(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT, "0"));
295+
//updatedPref.setSummary(getResources().getStringArray(R.array.candidate_timeout_entries)[candidateTimeout]);
296+
updatedPref.setSummary(candidateTimeoutValue2Summary(prefs.getString(RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT, "0")));
297+
}
298+
updated = true;
299+
}
275300
else if (key.equals(RCConnection.ParameterKeys.CONNECTION_PREFERRED_AUDIO_CODEC)) {
276301
params.put(RCConnection.ParameterKeys.CONNECTION_PREFERRED_AUDIO_CODEC, prefs.getString(RCConnection.ParameterKeys.CONNECTION_PREFERRED_AUDIO_CODEC, "Default"));
277302
Preference updatedPref = settingsFragment.findPreference(key);
@@ -318,4 +343,15 @@ public void onClick(DialogInterface dialog, int which)
318343
});
319344
alertDialog.show();
320345
}
346+
347+
private String candidateTimeoutValue2Summary(String value)
348+
{
349+
String summary = "No timeout";
350+
if (Integer.parseInt(value) > 0) {
351+
summary = value + " " + "seconds";
352+
}
353+
return summary;
354+
}
355+
356+
321357
}

Examples/restcomm-olympus/app/src/main/res/values/arrays.xml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,20 @@
9696
<item>2</item>
9797
</string-array>
9898

99-
99+
<string-array name="candidate_timeout_entries">
100+
<item>No timeout</item>
101+
<item>3 seconds</item>
102+
<item>5 seconds</item>
103+
<item>10 seconds</item>
104+
<item>20 seconds</item>
105+
</string-array>
106+
<string-array name="candidate_timeout_values">
107+
<item>0</item>
108+
<item>3</item>
109+
<item>5</item>
110+
<item>10</item>
111+
<item>20</item>
112+
</string-array>
100113

101114
</resources>
102115

Examples/restcomm-olympus/app/src/main/res/xml/preferences.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@
7272
android:dialogTitle="@string/pref_ice_servers_discovery_type_dlg"
7373
android:entries="@array/ice_servers_discovery_types_entries"
7474
android:entryValues="@array/ice_servers_discovery_types_values"/>
75+
<ListPreference
76+
android:key="debug-connection-candidate-timeout"
77+
android:title="@string/pref_candidate_timeout"
78+
android:defaultValue="@string/pref_candidate_timeout_default"
79+
android:dialogTitle="@string/pref_candidate_timeout_dlg"
80+
android:entries="@array/candidate_timeout_entries"
81+
android:entryValues="@array/candidate_timeout_values"/>
7582
<EditTextPreference
7683
android:defaultValue="https://es.xirsys.com/_turn"
7784
android:key="turn-url"

restcomm.android.sdk/src/main/java/org/restcomm/android/sdk/RCClient.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public enum ErrorCodes {
9191
ERROR_CONNECTION_AUDIO_CALL_VIDEO_CODEC_FORBIDDEN,
9292
ERROR_CONNECTION_AUDIO_CALL_VIDEO_RESOLUTION_FORBIDDEN,
9393
ERROR_CONNECTION_AUDIO_CALL_VIDEO_FRAME_RATE_FORBIDDEN,
94+
ERROR_CONNECTION_WEBRTC_CANDIDATES_TIMED_OUT,
9495

9596
ERROR_MESSAGE_AUTHENTICATION_FORBIDDEN,
9697
ERROR_MESSAGE_URI_INVALID,
@@ -271,6 +272,9 @@ else if (errorCode == ErrorCodes.ERROR_CONNECTION_AUDIO_CALL_VIDEO_RESOLUTION_FO
271272
else if (errorCode == ErrorCodes.ERROR_CONNECTION_AUDIO_CALL_VIDEO_FRAME_RATE_FORBIDDEN) {
272273
return "Failed to initiate connection due to parameter validation error; video frame rate not allowed to be specified in an audio call";
273274
}
275+
else if (errorCode == ErrorCodes.ERROR_CONNECTION_WEBRTC_CANDIDATES_TIMED_OUT) {
276+
return "Failed to collect any candidates on time; please check your network settings and connectivity or consider increasing candidate timeout";
277+
}
274278

275279
else if (errorCode == ErrorCodes.ERROR_MESSAGE_AUTHENTICATION_FORBIDDEN) {
276280
return "Message failed to authenticate with Service";

restcomm.android.sdk/src/main/java/org/restcomm/android/sdk/RCConnection.java

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ public static class ParameterKeys {
265265
// Incoming headers from Restcomm both for incoming and outgoing calls
266266
public static final String CONNECTION_CUSTOM_INCOMING_SIP_HEADERS = "sip-headers-incoming";
267267
public static final String CONNECTION_SIP_HEADER_KEY_CALL_SID = "X-RestComm-CallSid";
268+
269+
// Until we have trickle, as a way to timeout sooner than 40 seconds (webrtc default timeout)
270+
public static final String DEBUG_CONNECTION_CANDIDATE_TIMEOUT = "debug-connection-candidate-timeout";
268271
}
269272

270273
/**
@@ -382,6 +385,8 @@ public RCConnection build()
382385
private Handler timeoutHandler = null;
383386
// call times out if it hasn't been established after 15 seconds
384387
private final int CALL_TIMEOUT_DURATION_MILIS = 15 * 1000;
388+
private Handler candidateTimeoutHandler = null;
389+
private boolean iceGatheringCompleteCalled = false;
385390
// Device was already busy with another Connection when this Connection arrived. If so we need to set this so that we have custom behavior later
386391
private boolean deviceAlreadyBusy = false;
387392

@@ -445,6 +450,7 @@ private RCConnection(Builder builder)
445450
peer = builder.peer;
446451
deviceAlreadyBusy = builder.deviceAlreadyBusy;
447452
timeoutHandler = new Handler(device.getMainLooper());
453+
candidateTimeoutHandler = new Handler(device.getMainLooper());
448454

449455
callParams = new HashMap<>();
450456
if (builder.customHeaders != null) {
@@ -536,6 +542,11 @@ public void open(Map<String, Object> parameters)
536542
* <b>RCConnection.ParameterKeys.CONNECTION_PREFERRED_VIDEO_FRAME_RATE</b>: Preferred frame rate to use. Default is 30fps. Possible values are enumerated at <i>RCConnection.VideoFrameRate</i> <br>
537543
* <b>RCConnection.ParameterKeys.CONNECTION_CUSTOM_SIP_HEADERS</b>: An optional HashMap&lt;String,String&gt; of custom SIP headers we want to add. For an example
538544
* please check restcomm-helloworld or restcomm-olympus sample Apps (optional) <br>
545+
* <b>RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT</b>: An optional Integer denoting how long to wait for ICE candidates. Zero means default behaviour which is
546+
* to depend on onIceGatheringComplete from Peer Connection facilities. Any other integer value means to wait at most that amount of time no matter if onIceGatheringComplete has fired.
547+
* The problem we are addressing here is the new Peer Connection ICE gathering timeout which is 40 seconds which is way too long. Notice that the root cause here is in reality
548+
* lack of support for Trickle ICE, so once it is supported we won't be needing such workarounds.
549+
* please check restcomm-helloworld or restcomm-olympus sample Apps (optional) <br>
539550
*/
540551
public void accept(Map<String, Object> parameters)
541552
{
@@ -1062,6 +1073,7 @@ public void onCallErrorEvent(String jobId, RCClient.ErrorCodes errorCode, String
10621073
private void handleDisconnected(String jobId, boolean haveDisconnectedLocally)
10631074
{
10641075
timeoutHandler.removeCallbacksAndMessages(null);
1076+
candidateTimeoutHandler.removeCallbacksAndMessages(null);
10651077

10661078
// Device was already busy with another Connection, skip all handling here
10671079
if (deviceAlreadyBusy) {
@@ -1110,6 +1122,7 @@ private void handleDisconnect(String reason)
11101122
{
11111123
RCLogger.i(TAG, "handleDisconnect(): reason: " + reason);
11121124
timeoutHandler.removeCallbacksAndMessages(null);
1125+
candidateTimeoutHandler.removeCallbacksAndMessages(null);
11131126

11141127
audioManager.stop();
11151128

@@ -1179,6 +1192,22 @@ public void onIceServersReady(final LinkedList<PeerConnection.IceServer> iceServ
11791192
@Override
11801193
public void run()
11811194
{
1195+
1196+
if (RCConnection.this.callParams.containsKey(ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT) &&
1197+
(Integer) RCConnection.this.callParams.get(ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT) != 0) {
1198+
// cancel any pending timers before we start new one
1199+
candidateTimeoutHandler.removeCallbacksAndMessages(null);
1200+
Runnable runnable = new Runnable() {
1201+
@Override
1202+
public void run()
1203+
{
1204+
onCandidatesTimeout();
1205+
}
1206+
};
1207+
candidateTimeoutHandler.postDelayed(runnable, (Integer) RCConnection.this.callParams.get(ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT) * 1000);
1208+
//candidateTimeoutHandler.postDelayed(runnable, 150);
1209+
}
1210+
11821211
if (!RCConnection.this.incoming) {
11831212
// we are the initiator
11841213

@@ -1392,6 +1421,35 @@ private void onCallTimeout()
13921421
sendQoSDisconnectErrorIntent(errorCode.ordinal(), RCClient.errorText(errorCode));
13931422
}
13941423

1424+
private void onCandidatesTimeout()
1425+
{
1426+
RCLogger.e(TAG, "onCandidatesTimeout: Candidates timed out after: " + RCConnection.this.callParams.get(ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT) + " seconds");
1427+
1428+
if (signalingParameters != null && signalingParameters.iceCandidates != null &&
1429+
signalingParameters.iceCandidates.size() > 0) {
1430+
RCLogger.w(TAG, "onCandidatesTimeout: Managed to collect: " + signalingParameters.iceCandidates.size() + " candidates");
1431+
onIceGatheringComplete();
1432+
}
1433+
else {
1434+
// no candidates are gathered
1435+
handleDisconnect(null);
1436+
1437+
if (RCDevice.state == RCDevice.DeviceState.BUSY) {
1438+
RCDevice.state = RCDevice.DeviceState.READY;
1439+
}
1440+
1441+
if (device.isAttached()) {
1442+
RCConnection.this.listener.onError(RCConnection.this, RCClient.ErrorCodes.ERROR_CONNECTION_WEBRTC_CANDIDATES_TIMED_OUT.ordinal(),
1443+
RCClient.errorText(RCClient.ErrorCodes.ERROR_CONNECTION_WEBRTC_CANDIDATES_TIMED_OUT));
1444+
}
1445+
else {
1446+
RCLogger.w(TAG, "RCConnectionListener event suppressed since Restcomm Client Service not attached: onDisconnected()");
1447+
}
1448+
1449+
}
1450+
}
1451+
1452+
13951453
/*
13961454
// DEBUG (Issue #380)
13971455
public void off()
@@ -1920,24 +1978,33 @@ public void onIceGatheringComplete()
19201978
public void run()
19211979
{
19221980
RCLogger.i(TAG, "onIceGatheringComplete");
1923-
if (peerConnectionClient == null) {
1924-
// if the user hangs up the call before its setup we need to bail
1925-
return;
1926-
}
1927-
if (signalingParameters.initiator) {
1928-
HashMap<String, Object> parameters = new HashMap<String, Object>();
1929-
parameters.put(RCConnection.ParameterKeys.CONNECTION_PEER, signalingParameters.sipUrl);
1930-
parameters.put("sdp", connection.signalingParameters.generateSipSdp(connection.signalingParameters.offerSdp, connection.signalingParameters.iceCandidates));
1931-
parameters.put(ParameterKeys.CONNECTION_CUSTOM_SIP_HEADERS, connection.signalingParameters.sipHeaders);
19321981

1933-
signalingClient.call(jobId, parameters);
1982+
candidateTimeoutHandler.removeCallbacksAndMessages(null);
1983+
1984+
if (!iceGatheringCompleteCalled) {
1985+
iceGatheringCompleteCalled = true;
1986+
1987+
if (peerConnectionClient == null) {
1988+
// if the user hangs up the call before its setup we need to bail
1989+
return;
1990+
}
1991+
if (signalingParameters.initiator) {
1992+
HashMap<String, Object> parameters = new HashMap<String, Object>();
1993+
parameters.put(RCConnection.ParameterKeys.CONNECTION_PEER, signalingParameters.sipUrl);
1994+
parameters.put("sdp", connection.signalingParameters.generateSipSdp(connection.signalingParameters.offerSdp, connection.signalingParameters.iceCandidates));
1995+
parameters.put(ParameterKeys.CONNECTION_CUSTOM_SIP_HEADERS, connection.signalingParameters.sipHeaders);
1996+
1997+
signalingClient.call(jobId, parameters);
1998+
} else {
1999+
HashMap<String, Object> parameters = new HashMap<>();
2000+
parameters.put("sdp", connection.signalingParameters.generateSipSdp(connection.signalingParameters.answerSdp,
2001+
connection.signalingParameters.iceCandidates));
2002+
signalingClient.accept(jobId, parameters);
2003+
//connection.state = ConnectionState.CONNECTING;
2004+
}
19342005
}
19352006
else {
1936-
HashMap<String, Object> parameters = new HashMap<>();
1937-
parameters.put("sdp", connection.signalingParameters.generateSipSdp(connection.signalingParameters.answerSdp,
1938-
connection.signalingParameters.iceCandidates));
1939-
signalingClient.accept(jobId, parameters);
1940-
//connection.state = ConnectionState.CONNECTING;
2007+
RCLogger.w(TAG, "onIceGatheringComplete() already called, skipping");
19412008
}
19422009
}
19432010
};

restcomm.android.sdk/src/main/java/org/restcomm/android/sdk/RCDevice.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,11 @@ public void updateCapabilityToken(String token)
652652
* <b>RCConnection.ParameterKeys.CONNECTION_PREFERRED_VIDEO_FRAME_RATE</b>: Preferred frame rate to use. Default is 30fps. Possible values are enumerated at <i>RCConnection.VideoFrameRate</i> (optional) <br>
653653
* <b>RCConnection.ParameterKeys.CONNECTION_CUSTOM_SIP_HEADERS</b>: An optional HashMap&lt;String,String&gt; of custom SIP headers we want to add. For an example
654654
* please check restcomm-helloworld or restcomm-olympus sample Apps (optional) <br>
655+
* <b>RCConnection.ParameterKeys.DEBUG_CONNECTION_CANDIDATE_TIMEOUT</b>: An optional Integer denoting how long to wait for ICE candidates. Zero means default behaviour which is
656+
* to depend on onIceGatheringComplete from Peer Connection facilities. Any other integer value means to wait at most that amount of time no matter if onIceGatheringComplete has fired.
657+
* The problem we are addressing here is the new Peer Connection ICE gathering timeout which is 40 seconds which is way too long. Notice that the root cause here is in reality
658+
* lack of support for Trickle ICE, so once it is supported we won't be needing such workarounds.
659+
* please check restcomm-helloworld or restcomm-olympus sample Apps (optional) <br>
655660
* @param listener The listener object that will receive events when the connection state changes
656661
* @return An RCConnection object representing the new connection or null in case of error. Error
657662
* means that RCDevice.state not ready to make a call (this usually means no WiFi available)

restcomm.android.sdk/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
<string name="pref_ice_servers_discovery_type_dlg">Select ICE Servers Discovery Type</string>
5757
<string name="pref_ice_servers_discovery_type_default">1</string>
5858

59+
<string name="pref_candidate_timeout">Candidate timeout</string>
60+
<string name="pref_candidate_timeout_dlg">Select candidate timeout</string>
61+
<string name="pref_candidate_timeout_default">0</string>
62+
5963
<string name="pref_startvideobitrate_key">startvideobitrate_preference</string>
6064
<string name="pref_startvideobitrate_title">Start video bitrate setting.</string>
6165
<string name="pref_startvideobitrate_dlg">Start video bitrate setting.</string>

0 commit comments

Comments
 (0)