diff --git a/openvidu-java-client/pom.xml b/openvidu-java-client/pom.xml
index 501181a8..4be6cd83 100644
--- a/openvidu-java-client/pom.xml
+++ b/openvidu-java-client/pom.xml
@@ -86,6 +86,11 @@
${version.junit}
test
+
+ commons-validator
+ commons-validator
+ ${version.commons-validator}
+
diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/Connection.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/Connection.java
index fffa61b0..3e7ba5dd 100644
--- a/openvidu-java-client/src/main/java/io/openvidu/java/client/Connection.java
+++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/Connection.java
@@ -324,6 +324,11 @@ public class Connection {
if (this.connectionProperties.getNetworkCache() != null) {
builder.networkCache(this.connectionProperties.getNetworkCache());
}
+ if (this.connectionProperties.getCustomIceServers() != null && !this.connectionProperties.getCustomIceServers().isEmpty()) {
+ for (IceServerProperties iceServerProperties: this.connectionProperties.getCustomIceServers()) {
+ builder.addCustomIceServer(iceServerProperties);
+ }
+ }
this.connectionProperties = builder.build();
}
@@ -417,6 +422,24 @@ public class Connection {
? OpenViduRole.valueOf(json.get("role").getAsString())
: null;
+ List customIceServers = new ArrayList<>();
+ if (json.has("customIceServers") && json.get("customIceServers").isJsonArray()) {
+ JsonArray customIceServersJsonArray = json.get("customIceServers").getAsJsonArray();
+ customIceServersJsonArray.forEach(iceJsonElem -> {
+ JsonObject iceJsonObj = iceJsonElem.getAsJsonObject();
+ String url = (iceJsonObj.has("url") && !iceJsonObj.get("url").isJsonNull())
+ ? json.get("url").getAsString()
+ : null;
+ String username = (iceJsonObj.has("username") && !iceJsonObj.get("username").isJsonNull())
+ ? json.get("username").getAsString()
+ : null;
+ String credential = (iceJsonObj.has("credential") && !iceJsonObj.get("credential").isJsonNull())
+ ? json.get("credential").getAsString()
+ : null;
+ customIceServers.add(new IceServerProperties.Builder().url(url).username(username).credential(credential).build());
+ });
+ }
+
// IPCAM
String rtspUri = (json.has("rtspUri") && !json.get("rtspUri").isJsonNull()) ? json.get("rtspUri").getAsString()
: null;
@@ -431,25 +454,6 @@ public class Connection {
? json.get("networkCache").getAsInt()
: null;
- // External Ice Servers
- List customIceServers = new ArrayList<>();
- if (json.has("customIceServers") && json.get("customIceServers").isJsonArray()) {
- JsonArray customIceServersJsonArray = json.get("customIceServers").getAsJsonArray();
- customIceServersJsonArray.forEach(iceJsonElem -> {
- JsonObject iceJsonObj = iceJsonElem.getAsJsonObject();
- String url = (iceJsonObj.has("urls") && !iceJsonObj.get("urls").isJsonNull())
- ? json.get("urls").getAsString()
- : null;
- String username = (iceJsonObj.has("username") && !iceJsonObj.get("username").isJsonNull())
- ? json.get("username").getAsString()
- : null;
- String credential = (iceJsonObj.has("credential") && !iceJsonObj.get("credential").isJsonNull())
- ? json.get("credential").getAsString()
- : null;
- customIceServers.add(new IceServerProperties.Builder().url(url).username(username).credential(credential).build());
- });
- }
-
this.connectionProperties = new ConnectionProperties(type, data, record, role, null, rtspUri, adaptativeBitrate,
onlyPlayWithSubscribers, networkCache, customIceServers);
diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/ConnectionProperties.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/ConnectionProperties.java
index 1da3b382..38903f42 100644
--- a/openvidu-java-client/src/main/java/io/openvidu/java/client/ConnectionProperties.java
+++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/ConnectionProperties.java
@@ -399,6 +399,12 @@ public class ConnectionProperties {
} else {
json.add("kurentoOptions", JsonNull.INSTANCE);
}
+ JsonArray customIceServersJsonList = new JsonArray();
+ customIceServers.forEach((customIceServer) -> {
+ customIceServersJsonList.add(customIceServer.toJson());
+ });
+ json.add("customIceServers", customIceServersJsonList);
+
// IPCAM
if (getRtspUri() != null) {
json.addProperty("rtspUri", getRtspUri());
@@ -420,14 +426,6 @@ public class ConnectionProperties {
} else {
json.add("networkCache", JsonNull.INSTANCE);
}
-
- // Ice Servers
- JsonArray customIceServersJsonList = new JsonArray();
- customIceServers.forEach((customIceServer) -> {
- customIceServersJsonList.add(customIceServer.toJson());
- });
- json.add("customIceServers", customIceServersJsonList);
-
return json;
}
diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java
index b6a5de9e..565b6a6e 100644
--- a/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java
+++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java
@@ -1,7 +1,11 @@
package io.openvidu.java.client;
import com.google.gson.JsonObject;
+import org.apache.commons.validator.routines.DomainValidator;
+import org.apache.commons.validator.routines.InetAddressValidator;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -37,7 +41,7 @@ public class IceServerProperties {
*/
public JsonObject toJson() {
JsonObject json = new JsonObject();
- json.addProperty("urls", getUrl());
+ json.addProperty("url", getUrl());
if (getUsername() != null && !getUsername().isEmpty()) {
json.addProperty("username", getUsername());
}
@@ -74,13 +78,15 @@ public class IceServerProperties {
throw new IllegalArgumentException("External turn url cannot be null");
}
this.checkValidStunTurn(this.url);
- if (this.username == null ^ this.credential == null) {
- // If one is null when the other is defined, fail...
- throw new IllegalArgumentException("You need to define username and credentials if you define one of them");
- }
- if (this.username != null && this.credential != null && this.url.startsWith("stun")) {
- // Credentials can not be defined using stun
- throw new IllegalArgumentException("Credentials can not be defined while using stun.");
+ if (this.url.startsWith("turn")) {
+ if ((this.username == null || this.credential == null)) {
+ throw new IllegalArgumentException("Credentials must be defined while using turn");
+ }
+ } else if (this.url.startsWith("stun")) {
+ if (this.username != null || this.credential != null) {
+ // Credentials can not be defined using stun
+ throw new IllegalArgumentException("Credentials can not be defined while using stun.");
+ }
}
return new IceServerProperties(this.url, this.username, this.credential);
}
@@ -138,32 +144,88 @@ public class IceServerProperties {
// Check if port is defined
int portColon = hostAndPort.indexOf(':');
- if (portColon != -1) {
- String[] splittedHostAndPort = hostAndPort.split(":");
- if (splittedHostAndPort.length != 2) {
- throw new IllegalArgumentException("Host or port are not correctly " +
- "defined in STUN/TURN uri: '" + uri + "'");
- }
- String host = splittedHostAndPort[0];
- String port = splittedHostAndPort[1];
-
- // Check if host is defined. Valid Host (Domain or IP) will be done at server side
- if (host == null || host.isEmpty()) {
- throw new IllegalArgumentException("Host defined in '" + uri + "' is empty or null");
- }
-
- if (port == null || port.isEmpty()) {
- throw new IllegalArgumentException("Port defined in '" + uri + "' is empty or null");
- }
- try {
- int parsedPort = Integer.parseInt(port);
- if (parsedPort <= 0 || parsedPort > 65535) {
- throw new IllegalArgumentException("The port defined in '" + uri + "' is not a valid port number");
- }
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("The port defined in '" + uri + "' is not a number");
- }
+ // IPv6 are defined between brackets
+ int startIpv6Index = hostAndPort.indexOf('[');
+ int endIpv6Index = hostAndPort.indexOf(']');
+ if (startIpv6Index == -1 ^ endIpv6Index == -1) {
+ throw new IllegalArgumentException("Not closed bracket '[' or ']' in uri: " + uri);
}
+
+ if (portColon != -1) {
+ if (startIpv6Index == -1 && endIpv6Index == -1) {
+ // If Ipv4 and port defined
+ String[] splittedHostAndPort = hostAndPort.split(":");
+ if (splittedHostAndPort.length != 2) {
+ throw new IllegalArgumentException("Host or port are not correctly " +
+ "defined in STUN/TURN uri: '" + uri + "'");
+ }
+ String host = splittedHostAndPort[0];
+ String port = splittedHostAndPort[1];
+
+ // Check if host is defined. Valid Host (Domain or IP) will be done at server side
+ checkHostAndPort(uri, host, port);
+ } else {
+ // If portColon is found and Ipv6
+ String ipv6 = hostAndPort.substring(startIpv6Index + 1, endIpv6Index);
+ String auxPort = hostAndPort.substring(endIpv6Index + 1);
+ if (auxPort.startsWith(":")) {
+ if (auxPort.length() == 1) {
+ throw new IllegalArgumentException("Host or port are not correctly defined in STUN/TURN uri: " + uri);
+ }
+ // If port is defined
+ // Get port without colon and check host and port
+ String host = ipv6;
+ String port = auxPort.substring(1);
+ checkHostAndPort(uri, host, port);
+ } else if (auxPort.length() > 0) {
+ // If auxPort = 0, no port is defined
+ throw new IllegalArgumentException("Port is not specified correctly after IPv6 in uri: '" + uri + "'");
+ }
+ }
+ } else {
+ // If portColon not found, only host is defined
+ String host = hostAndPort;
+ checkHost(uri, host);
+ }
+ }
+
+ private void checkHost(String uri, String host) {
+ if (host == null || host.isEmpty()) {
+ throw new IllegalArgumentException("Host defined in '" + uri + "' is empty or null");
+ }
+ if (DomainValidator.getInstance().isValid(host)) {
+ return;
+ }
+ InetAddressValidator ipValidator = InetAddressValidator.getInstance();
+ if (ipValidator.isValid(host)) {
+ return;
+ }
+ try {
+ Inet6Address.getByName(host).getHostAddress();
+ return;
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("Is not a valid Internet Address (IP or Domain Name): '" + host + "'");
+ }
+ }
+
+ private void checkPort(String uri, String port) {
+ if (port == null || port.isEmpty()) {
+ throw new IllegalArgumentException("Port defined in '" + uri + "' is empty or null");
+ }
+
+ try {
+ int parsedPort = Integer.parseInt(port);
+ if (parsedPort <= 0 || parsedPort > 65535) {
+ throw new IllegalArgumentException("The port defined in '" + uri + "' is not a valid port number (0-65535)");
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("The port defined in '" + uri + "' is not a number (0-65535)");
+ }
+ }
+
+ private void checkHostAndPort(String uri, String host, String port) {
+ this.checkHost(uri, host);
+ this.checkPort(uri, port);
}
}
diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java
index 9821b2a2..3b249ea8 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java
@@ -19,6 +19,7 @@ package io.openvidu.server.core;
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
@@ -51,6 +52,7 @@ import io.openvidu.java.client.KurentoOptions;
import io.openvidu.java.client.OpenViduRole;
import io.openvidu.java.client.Recording;
import io.openvidu.java.client.SessionProperties;
+import io.openvidu.java.client.IceServerProperties;
import io.openvidu.server.cdr.CDREventRecordingStatusChanged;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.coturn.CoturnCredentialsService;
@@ -329,13 +331,13 @@ public abstract class SessionManager {
}
public Token newToken(Session session, OpenViduRole role, String serverMetadata, boolean record,
- KurentoOptions kurentoOptions) throws Exception {
+ KurentoOptions kurentoOptions, List customIceServers) throws Exception {
if (!formatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
log.error("Data invalid format");
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
}
Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), serverMetadata, record, role,
- kurentoOptions);
+ kurentoOptions, customIceServers);
// Internal dev feature: allows customizing connectionId
if (serverMetadata.contains("openviduCustomConnectionId")) {
diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/Token.java b/openvidu-server/src/main/java/io/openvidu/server/core/Token.java
index 09d68bac..6633a761 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/core/Token.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/core/Token.java
@@ -17,18 +17,17 @@
package io.openvidu.server.core;
+import io.openvidu.java.client.*;
import org.apache.commons.lang3.RandomStringUtils;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
-import io.openvidu.java.client.ConnectionProperties;
-import io.openvidu.java.client.ConnectionType;
-import io.openvidu.java.client.KurentoOptions;
-import io.openvidu.java.client.OpenViduRole;
import io.openvidu.server.core.Participant.ParticipantStatus;
import io.openvidu.server.coturn.TurnCredentials;
+import java.util.List;
+
public class Token {
private String token;
@@ -77,7 +76,8 @@ public class Token {
this.updateConnectionProperties(connectionProperties.getType(), connectionProperties.getData(), newRecord,
connectionProperties.getRole(), connectionProperties.getKurentoOptions(),
connectionProperties.getRtspUri(), connectionProperties.adaptativeBitrate(),
- connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache());
+ connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache(),
+ connectionProperties.getCustomIceServers());
}
public OpenViduRole getRole() {
@@ -88,7 +88,8 @@ public class Token {
this.updateConnectionProperties(connectionProperties.getType(), connectionProperties.getData(),
connectionProperties.record(), newRole, connectionProperties.getKurentoOptions(),
connectionProperties.getRtspUri(), connectionProperties.adaptativeBitrate(),
- connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache());
+ connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache(),
+ connectionProperties.getCustomIceServers());
}
public KurentoOptions getKurentoOptions() {
@@ -118,6 +119,10 @@ public class Token {
public String getConnectionId() {
return connectionId;
}
+
+ public List getCustomIceServers() {
+ return this.connectionProperties.getCustomIceServers();
+ }
public void setConnectionId(String connectionId) {
this.connectionId = connectionId;
@@ -178,7 +183,7 @@ public class Token {
private void updateConnectionProperties(ConnectionType type, String data, Boolean record, OpenViduRole role,
KurentoOptions kurentoOptions, String rtspUri, Boolean adaptativeBitrate, Boolean onlyPlayWithSubscribers,
- Integer networkCache) {
+ Integer networkCache, List iceServerProperties) {
ConnectionProperties.Builder builder = new ConnectionProperties.Builder();
if (type != null) {
builder.type(type);
@@ -207,6 +212,11 @@ public class Token {
if (networkCache != null) {
builder.networkCache(networkCache);
}
+ if (iceServerProperties != null) {
+ for (IceServerProperties customIceServer: iceServerProperties) {
+ builder.addCustomIceServer(customIceServer);
+ }
+ }
this.connectionProperties = builder.build();
}
diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/TokenGenerator.java b/openvidu-server/src/main/java/io/openvidu/server/core/TokenGenerator.java
index 35192b52..7245d19e 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/core/TokenGenerator.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/core/TokenGenerator.java
@@ -24,12 +24,15 @@ import io.openvidu.java.client.ConnectionProperties;
import io.openvidu.java.client.ConnectionType;
import io.openvidu.java.client.KurentoOptions;
import io.openvidu.java.client.OpenViduRole;
+import io.openvidu.java.client.IceServerProperties;
import io.openvidu.server.OpenViduServer;
import io.openvidu.server.config.OpenviduBuildInfo;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.coturn.CoturnCredentialsService;
import io.openvidu.server.coturn.TurnCredentials;
+import java.util.List;
+
public class TokenGenerator {
@Autowired
@@ -42,7 +45,7 @@ public class TokenGenerator {
protected OpenviduBuildInfo openviduBuildConfig;
public Token generateToken(String sessionId, String serverMetadata, boolean record, OpenViduRole role,
- KurentoOptions kurentoOptions) throws Exception {
+ KurentoOptions kurentoOptions, List customIceServers) throws Exception {
String token = OpenViduServer.wsUrl;
token += "?sessionId=" + sessionId;
token += "&token=" + IdentifierPrefixes.TOKEN_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase()
@@ -51,8 +54,13 @@ public class TokenGenerator {
if (this.openviduConfig.isTurnadminAvailable()) {
turnCredentials = coturnCredentialsService.createUser();
}
- ConnectionProperties connectionProperties = new ConnectionProperties.Builder().type(ConnectionType.WEBRTC)
- .data(serverMetadata).record(record).role(role).kurentoOptions(kurentoOptions).build();
+ ConnectionProperties.Builder connectionPropertiesBuilder = new ConnectionProperties.Builder()
+ .type(ConnectionType.WEBRTC).data(serverMetadata).record(record).role(role)
+ .kurentoOptions(kurentoOptions);
+ for (IceServerProperties customIceServer: customIceServers) {
+ connectionPropertiesBuilder.addCustomIceServer(customIceServer);
+ }
+ ConnectionProperties connectionProperties = connectionPropertiesBuilder.build();
return new Token(token, sessionId, connectionProperties, turnCredentials);
}
diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java
index c7581d4c..1e3bc555 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java
@@ -21,6 +21,7 @@ import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
@@ -653,7 +654,7 @@ public class SessionRestController {
try {
Token token = sessionManager.newToken(session, connectionProperties.getRole(),
connectionProperties.getData(), connectionProperties.record(),
- connectionProperties.getKurentoOptions());
+ connectionProperties.getKurentoOptions(), connectionProperties.getCustomIceServers());
return new ResponseEntity<>(token.toJsonAsParticipant().toString(), RestUtils.getResponseHeaders(),
HttpStatus.OK);
} catch (Exception e) {
@@ -909,7 +910,7 @@ public class SessionRestController {
JsonArray customIceServersJsonArray = null;
if (params.get("customIceServers") != null) {
try {
- customIceServersJsonArray = new Gson().toJsonTree(params.get("customIceServers"), Map.class)
+ customIceServersJsonArray = new Gson().toJsonTree(params.get("customIceServers"), List.class)
.getAsJsonArray();
} catch (Exception e) {
throw new Exception("Error in parameter 'customIceServersJson'. It is not a valid JSON object");
@@ -920,7 +921,7 @@ public class SessionRestController {
for (int i = 0; i < customIceServersJsonArray.size(); i++) {
JsonObject customIceServerJson = customIceServersJsonArray.get(i).getAsJsonObject();
IceServerProperties.Builder iceServerPropertiesBuilder = new IceServerProperties.Builder();
- iceServerPropertiesBuilder.url(customIceServerJson.get("urls").getAsString());
+ iceServerPropertiesBuilder.url(customIceServerJson.get("url").getAsString());
if (customIceServerJson.has("username")) {
iceServerPropertiesBuilder.username(customIceServerJson.get("username").getAsString());
}
@@ -931,7 +932,7 @@ public class SessionRestController {
builder.addCustomIceServer(iceServerProperties);
}
} catch (Exception e) {
- throw new Exception("Type error in some parameter of 'kurentoOptions': " + e.getMessage());
+ throw new Exception("Type error in some parameter of 'customIceServers': " + e.getMessage());
}
}
diff --git a/openvidu-server/src/test/java/io/openvidu/server/test/unit/IceServerPropertiesTests.java b/openvidu-server/src/test/java/io/openvidu/server/test/unit/IceServerPropertiesTests.java
new file mode 100644
index 00000000..b3d8db1f
--- /dev/null
+++ b/openvidu-server/src/test/java/io/openvidu/server/test/unit/IceServerPropertiesTests.java
@@ -0,0 +1,251 @@
+package io.openvidu.server.test.unit;
+
+import io.openvidu.java.client.IceServerProperties;
+import org.junit.Test;
+import org.junit.jupiter.api.DisplayName;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class IceServerPropertiesTests {
+
+ @Test
+ @DisplayName("IceServerProperty exceptions tests")
+ public void iceServerPropertiesExceptionTest() {
+ // Wrong urls
+ notValidIceServerTest(
+ "wrongurl", null, null,
+ "Not a valid TURN/STUN uri provided. No colons found in: 'wrongurl'"
+ );
+ notValidIceServerTest(
+ "wrongurl", "anyuser", null,
+ "Not a valid TURN/STUN uri provided. No colons found in: 'wrongurl'"
+ );
+ notValidIceServerTest(
+ "wrongurl", null, "anypassword",
+ "Not a valid TURN/STUN uri provided. No colons found in: 'wrongurl'"
+ );
+ notValidIceServerTest(
+ "wrongurl", "anyuser", "anypassword",
+ "Not a valid TURN/STUN uri provided. No colons found in: 'wrongurl'"
+ );
+
+ // Wrong prefixes
+ notValidIceServerTest(
+ "turnss:wrongurl", null, null,
+ "The protocol 'turnss' is invalid. Only valid values are: [turn, turns] [stuns, stun]"
+ );
+ notValidIceServerTest(
+ "stunss:wrongurl", "anyuser", null,
+ "The protocol 'stunss' is invalid. Only valid values are: [turn, turns] [stuns, stun]"
+ );
+ notValidIceServerTest(
+ "anything:wrongurl", null, "anypassword",
+ "The protocol 'anything' is invalid. Only valid values are: [turn, turns] [stuns, stun]"
+ );
+ notValidIceServerTest(
+ ":", null, null,
+ "The protocol '' is invalid. Only valid values are: [turn, turns] [stuns, stun]"
+ );
+ notValidIceServerTest(
+ "", null, null,
+ "Not a valid TURN/STUN uri provided. No colons found in: ''"
+ );
+
+ // Try invalid host and ports
+ notValidIceServerTest(
+ "stun:hostname.com:99a99", null, null,
+ "The port defined in 'stun:hostname.com:99a99' is not a number (0-65535)"
+ );
+ notValidIceServerTest(
+ "stun:hostname.com:-1", null, null,
+ "The port defined in 'stun:hostname.com:-1' is not a valid port number (0-65535)"
+ );
+ notValidIceServerTest(
+ "stun:hostname:port:more", null, null,
+ "Host or port are not correctly defined in STUN/TURN uri: 'stun:hostname:port:more'"
+ );
+ notValidIceServerTest(
+ "stun:hostname.com:port more", null, null,
+ "The port defined in 'stun:hostname.com:port more' is not a number (0-65535)"
+ );
+ notValidIceServerTest(
+ "stun:hostname.com:", null, null,
+ "Host or port are not correctly defined in STUN/TURN uri: 'stun:hostname.com:'"
+ );
+ notValidIceServerTest(
+ "stun:[1:2:3:4:5:6:7:8]junk:1000", null, null,
+ "Port is not specified correctly after IPv6 in uri: 'stun:[1:2:3:4:5:6:7:8]junk:1000'"
+ );
+ notValidIceServerTest(
+ "stun:[notvalid:]:1000", null, null,
+ "Is not a valid Internet Address (IP or Domain Name): 'notvalid:'"
+ );
+ notValidIceServerTest(
+ "stun::5555", null, null,
+ "Host defined in 'stun::5555' is empty or null"
+ );
+ notValidIceServerTest(
+ "stun:", null, null,
+ "Host defined in 'stun:' is empty or null"
+ );
+
+ // Illegal Uri tests according to RFC 3986 and RFC 7064 (URI schemes for STUN and TURN)
+ notValidIceServerTest(
+ "stun:/hostname.com", null, null,
+ "Is not a valid Internet Address (IP or Domain Name): '/hostname.com'"
+ );
+ notValidIceServerTest(
+ "stun:?hostname.com", null, null,
+ "STUN uri can't have any '?' query param"
+ );
+ notValidIceServerTest(
+ "stun:#hostname.com", null, null,
+ "Is not a valid Internet Address (IP or Domain Name): '#hostname.com'"
+ );
+
+ // illegal ?transport=xxx tests in turn uris
+ notValidIceServerTest(
+ "turn:hostname.com?transport=invalid", "anyuser", "anypassword",
+ "Wrong value specified in STUN/TURN uri: 'turn:hostname.com?transport=invalid'. Unique valid arguments after '?' are '?transport=tcp' or '?transport=udp"
+ );
+ notValidIceServerTest(
+ "turn:hostname.com?transport=", "anyuser", "anypassword",
+ "Wrong value specified in STUN/TURN uri: 'turn:hostname.com?transport='. Unique valid arguments after '?' are '?transport=tcp' or '?transport=udp"
+ );
+ notValidIceServerTest(
+ "turn:hostname.com?=", "anyuser", "anypassword",
+ "Wrong value specified in STUN/TURN uri: 'turn:hostname.com?='. Unique valid arguments after '?' are '?transport=tcp' or '?transport=udp"
+ );
+ notValidIceServerTest(
+ "turn:hostname.com?", "anyuser", "anypassword",
+ "Wrong value specified in STUN/TURN uri: 'turn:hostname.com?'. Unique valid arguments after '?' are '?transport=tcp' or '?transport=udp"
+ );
+ notValidIceServerTest(
+ "?", "anyuser", "anypassword",
+ "Not a valid TURN/STUN uri provided. No colons found in: '?'"
+ );
+
+ // Transport can not be defined in STUN
+ notValidIceServerTest(
+ "stun:hostname.com?transport=tcp", null, null,
+ "STUN uri can't have any '?' query param"
+ );
+ notValidIceServerTest(
+ "stun:hostname.com?transport=udp", null, null,
+ "STUN uri can't have any '?' query param"
+ );
+
+ // Stun can not have credentials defined
+ notValidIceServerTest(
+ "stun:hostname.com", "username", "credential",
+ "Credentials can not be defined while using stun."
+ );
+ notValidIceServerTest(
+ "stun:hostname.com", "username", "credential",
+ "Credentials can not be defined while using stun."
+ );
+ notValidIceServerTest(
+ "stun:hostname.com", "username", null,
+ "Credentials can not be defined while using stun."
+ );
+ notValidIceServerTest(
+ "stun:hostname.com", null, "credential",
+ "Credentials can not be defined while using stun."
+ );
+
+ // Turn must have credentials
+ notValidIceServerTest(
+ "turn:hostname.com", null, null,
+ "Credentials must be defined while using turn"
+ );
+ notValidIceServerTest(
+ "turn:hostname.com", "username", null,
+ "Credentials must be defined while using turn"
+ );
+ notValidIceServerTest(
+ "turn:hostname.com", null, "credential",
+ "Credentials must be defined while using turn"
+ );
+ }
+
+ @Test
+ @DisplayName("IceServerProperty exceptions tests")
+ public void iceServerPropertiesValidTest() {
+ // Stun and stuns
+ validIceServerTest("stun:hostname.com", null, null);
+ validIceServerTest("stuns:hostname.com", null, null);
+
+ // Turn and turns
+ validIceServerTest("turn:hostname.com", "anyuser", "credential");
+ validIceServerTest("turns:hostname.com", "anyuser", "credential");
+
+ // Test IPv4/IPv6/hostname and with/without port
+ validIceServerTest("stun:1.2.3.4:1234", null, null);
+ validIceServerTest("stun:[1:2:3:4:5:6:7:8]:4321", null, null);
+ validIceServerTest("stun:hostname.com:9999", null, null);
+ validIceServerTest("stun:1.2.3.4", null, null);
+ validIceServerTest("stun:[1:2:3:4:5:6:7:8]", null, null);
+ validIceServerTest("stuns:1.2.3.4:1234", null, null);
+ validIceServerTest("stuns:[1:2:3:4:5:6:7:8]:4321", null, null);
+ validIceServerTest("stuns:hostname.com:9999", null, null);
+ validIceServerTest("stuns:1.2.3.4", null, null);
+ validIceServerTest("stuns:[1:2:3:4:5:6:7:8]", null, null);
+ validIceServerTest("turn:1.2.3.4:1234", "anyuser", "credential");
+ validIceServerTest("turn:[1:2:3:4:5:6:7:8]:4321", "anyuser", "credential");
+ validIceServerTest("turn:hostname.com:9999", "anyuser", "credential");
+ validIceServerTest("turn:1.2.3.4", "anyuser", "credential");
+ validIceServerTest("turn:[1:2:3:4:5:6:7:8]", "anyuser", "credential");
+ validIceServerTest("turns:1.2.3.4:1234", "anyuser", "credential");
+ validIceServerTest("turns:[1:2:3:4:5:6:7:8]:4321", "anyuser", "credential");
+ validIceServerTest("turns:hostname.com:9999", "anyuser", "credential");
+ validIceServerTest("turns:1.2.3.4", "anyuser", "credential");
+ validIceServerTest("turns:[1:2:3:4:5:6:7:8]", "anyuser", "credential");
+
+ // Test valid ?transport=tcp or ?transport=udp
+ validIceServerTest("turn:hostname.com:1234?transport=tcp", "anyuser", "credential");
+ validIceServerTest("turn:hostname.com?transport=udp", "anyuser", "credential");
+ validIceServerTest("turn:1.2.3.4:1234?transport=tcp", "anyuser", "credential");
+ validIceServerTest("turn:1.2.3.4?transport=udp", "anyuser", "credential");
+ validIceServerTest("turn:[1:2:3:4:5:6:7:8]:4321?transport=udp", "anyuser", "credential");
+ validIceServerTest("turn:[1:2:3:4:5:6:7:8]?transport=udp", "anyuser", "credential");
+ }
+
+ private void validIceServerTest(String url, String username, String credential) {
+ assertDoesNotThrow(() -> {
+ IceServerProperties.Builder iceServerPropertiesBuilder = new IceServerProperties.Builder().url(url);
+ if (username != null) {
+ iceServerPropertiesBuilder.username(username);
+ }
+ if (credential != null) {
+ iceServerPropertiesBuilder.credential(credential);
+ }
+ IceServerProperties iceServerProperties = iceServerPropertiesBuilder.build();
+ assertEquals(url, iceServerProperties.getUrl());
+ if (username != null) {
+ assertEquals(username, iceServerProperties.getUsername());
+ }
+ if (credential != null) {
+ assertEquals(credential, iceServerProperties.getCredential());
+ }
+ });
+ }
+
+ private void notValidIceServerTest(String url, String username, String credential, String expectedMessage) {
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ IceServerProperties.Builder iceServerPropertiesBuilder = new IceServerProperties.Builder().url(url);
+ if (username != null) {
+ iceServerPropertiesBuilder.username(username);
+ }
+ if (credential != null) {
+ iceServerPropertiesBuilder.credential(credential);
+ }
+ iceServerPropertiesBuilder.build();
+ });
+
+ String actualMessage = exception.getMessage();
+ assertEquals(actualMessage, expectedMessage);
+ }
+
+}