Tests for IceServerProperties. Integrate new attribute to Connection and generation token logic

pull/698/head
cruizba 2022-02-08 20:04:51 +01:00
parent 3274db8a61
commit fca9c7b2ab
9 changed files with 417 additions and 76 deletions

View File

@ -86,6 +86,11 @@
<version>${version.junit}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>${version.commons-validator}</version>
</dependency>
</dependencies>
<profiles>

View File

@ -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<IceServerProperties> 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<IceServerProperties> 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);

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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<IceServerProperties> 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")) {

View File

@ -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() {
@ -119,6 +120,10 @@ public class Token {
return connectionId;
}
public List<IceServerProperties> 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> 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();
}

View File

@ -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<IceServerProperties> 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);
}

View File

@ -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());
}
}

View File

@ -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);
}
}