From d7eae78372aea65d62b5f3bbd438f4937b70fe27 Mon Sep 17 00:00:00 2001 From: cruizba Date: Wed, 2 Feb 2022 18:08:35 +0100 Subject: [PATCH 01/11] Initial logic in openvidu-java-client to add to the ConnectionProperties class a new 'customIceServers' parameter --- .../io/openvidu/java/client/Connection.java | 24 ++- .../java/client/ConnectionProperties.java | 35 +++- .../java/client/IceServerProperties.java | 170 ++++++++++++++++++ 3 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java 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 b838e43e..fffa61b0 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 @@ -25,7 +25,9 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; /** * See {@link io.openvidu.java.client.Session#getConnections()} @@ -428,8 +430,28 @@ public class Connection { Integer networkCache = (json.has("networkCache") && !json.get("networkCache").isJsonNull()) ? 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); + onlyPlayWithSubscribers, networkCache, customIceServers); return this; } 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 b68504c3..1da3b382 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 @@ -1,8 +1,12 @@ package io.openvidu.java.client; +import com.google.gson.JsonArray; import com.google.gson.JsonNull; import com.google.gson.JsonObject; +import java.util.ArrayList; +import java.util.List; + /** * See * {@link io.openvidu.java.client.Session#createConnection(ConnectionProperties)} @@ -22,6 +26,9 @@ public class ConnectionProperties { private Boolean onlyPlayWithSubscribers; private Integer networkCache; + // External Turn Service + private List customIceServers; + /** * * Builder for {@link io.openvidu.java.client.ConnectionProperties} @@ -42,12 +49,16 @@ public class ConnectionProperties { private Boolean onlyPlayWithSubscribers; private Integer networkCache; + // External Turn Service + private List customIceServers = new ArrayList<>(); + /** * Builder for {@link io.openvidu.java.client.ConnectionProperties}. */ public ConnectionProperties build() { return new ConnectionProperties(this.type, this.data, this.record, this.role, this.kurentoOptions, - this.rtspUri, this.adaptativeBitrate, this.onlyPlayWithSubscribers, this.networkCache); + this.rtspUri, this.adaptativeBitrate, this.onlyPlayWithSubscribers, this.networkCache, + this.customIceServers); } /** @@ -219,11 +230,17 @@ public class ConnectionProperties { this.networkCache = networkCache; return this; } + + // TODO: Comment + public Builder addCustomIceServer(IceServerProperties iceServerProperties) { + this.customIceServers.add(iceServerProperties); + return this; + } } ConnectionProperties(ConnectionType type, String data, Boolean record, OpenViduRole role, KurentoOptions kurentoOptions, String rtspUri, Boolean adaptativeBitrate, Boolean onlyPlayWithSubscribers, - Integer networkCache) { + Integer networkCache, List customIceServers) { this.type = type; this.data = data; this.record = record; @@ -233,6 +250,7 @@ public class ConnectionProperties { this.adaptativeBitrate = adaptativeBitrate; this.onlyPlayWithSubscribers = onlyPlayWithSubscribers; this.networkCache = networkCache; + this.customIceServers = customIceServers; } /** @@ -346,6 +364,11 @@ public class ConnectionProperties { return this.networkCache; } + // TODO: Comment + public List getCustomIceServers() { + return new ArrayList<>(this.customIceServers); + } + public JsonObject toJson(String sessionId) { JsonObject json = new JsonObject(); json.addProperty("session", sessionId); @@ -397,6 +420,14 @@ 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 new file mode 100644 index 00000000..b6a5de9e --- /dev/null +++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java @@ -0,0 +1,170 @@ +package io.openvidu.java.client; + +import com.google.gson.JsonObject; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class IceServerProperties { + + private String url; + private String username; + private String credential; + + public String getUrl() { + return url; + } + + public String getUsername() { + return username; + } + + public String getCredential() { + return credential; + } + + private IceServerProperties(String url, String username, String credential) { + this.url = url; + this.username = username; + this.credential = credential; + } + + /** + * Ice server properties following RTCIceServers format: + * https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer/urls + * @return + */ + public JsonObject toJson() { + JsonObject json = new JsonObject(); + json.addProperty("urls", getUrl()); + if (getUsername() != null && !getUsername().isEmpty()) { + json.addProperty("username", getUsername()); + } + if (getCredential() != null && !getCredential().isEmpty()) { + json.addProperty("credential", getCredential()); + } + return json; + } + + public static class Builder { + + private String url; + private String username; + private String credential; + + public IceServerProperties.Builder url(String url) { + this.url = url; + return this; + } + + public IceServerProperties.Builder username(String userName) { + this.username = userName; + return this; + } + + public IceServerProperties.Builder credential(String credential) { + this.credential = credential; + return this; + } + + + public IceServerProperties build() throws IllegalArgumentException { + if (this.url == null) { + 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."); + } + return new IceServerProperties(this.url, this.username, this.credential); + } + + /** Parsing Turn Stun Uri based on: + * - https://datatracker.ietf.org/doc/html/rfc7065#section-3.1 + * - https://datatracker.ietf.org/doc/html/rfc7064#section-3.1 + */ + private void checkValidStunTurn(String uri) throws IllegalArgumentException { + final String TCP_TRANSPORT_SUFFIX = "?transport=tcp"; + final String UDP_TRANSPORT_SUFFIX = "?transport=udp"; + + // Protocols which accepts transport=tcp and transport=udp + final Set TURN_PROTOCOLS = new HashSet<>(Arrays.asList( + "turn", + "turns" + )); + final Set STUN_PROTOCOLS = new HashSet<>(Arrays.asList( + "stun", + "stuns" + )); + + // Fails if no colons + int firstColonPos = uri.indexOf(':'); + if (firstColonPos == -1) { + throw new IllegalArgumentException("Not a valid TURN/STUN uri provided. " + + "No colons found in: '" + uri + "'"); + } + + // Get protocol and check + String protocol = uri.substring(0, firstColonPos); + if (!TURN_PROTOCOLS.contains(protocol) && !STUN_PROTOCOLS.contains(protocol)) { + throw new IllegalArgumentException("The protocol '" + protocol + "' is invalid. Only valid values are: " + + TURN_PROTOCOLS + " " + STUN_PROTOCOLS); + } + + // Check if query param with transport exist + int qmarkPos = uri.indexOf('?'); + String hostAndPort = uri.substring(firstColonPos + 1); + if (qmarkPos != -1) { + if (TURN_PROTOCOLS.contains(protocol)) { + // Only Turn uses transport arg + String rawTransportType = uri.substring(qmarkPos); + hostAndPort = uri.substring(firstColonPos + 1, qmarkPos); + if (!TCP_TRANSPORT_SUFFIX.equals(rawTransportType) && !UDP_TRANSPORT_SUFFIX.equals(rawTransportType)) { + // If other argument rather than transport is specified, it is a wrong query for a STUN/TURN uri + throw new IllegalArgumentException("Wrong value specified in STUN/TURN uri: '" + + uri + "'. " + "Unique valid arguments after '?' are '" + + TCP_TRANSPORT_SUFFIX + "' or '" + UDP_TRANSPORT_SUFFIX); + } + } else { + throw new IllegalArgumentException("STUN uri can't have any '?' query param"); + } + } + + // 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"); + } + } + } + } + +} From ad778ff0d38668753fb2ab0e0ac66e394c5d505c Mon Sep 17 00:00:00 2001 From: cruizba Date: Wed, 2 Feb 2022 18:44:19 +0100 Subject: [PATCH 02/11] openvidu-server: Add customIceServers to REST post of connection --- .../server/rest/SessionRestController.java | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) 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 cc7d40e1..c7581d4c 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 @@ -26,6 +26,7 @@ import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import io.openvidu.java.client.*; import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,17 +52,7 @@ import com.google.gson.JsonParser; import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException.Code; import io.openvidu.client.internal.ProtocolElements; -import io.openvidu.java.client.ConnectionProperties; -import io.openvidu.java.client.ConnectionType; -import io.openvidu.java.client.KurentoOptions; -import io.openvidu.java.client.MediaMode; -import io.openvidu.java.client.OpenViduRole; import io.openvidu.java.client.Recording.OutputMode; -import io.openvidu.java.client.RecordingLayout; -import io.openvidu.java.client.RecordingMode; -import io.openvidu.java.client.RecordingProperties; -import io.openvidu.java.client.SessionProperties; -import io.openvidu.java.client.VideoCodec; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.EndReason; import io.openvidu.server.core.IdentifierPrefixes; @@ -914,6 +905,36 @@ public class SessionRestController { } } + // Custom Ice Servers + JsonArray customIceServersJsonArray = null; + if (params.get("customIceServers") != null) { + try { + customIceServersJsonArray = new Gson().toJsonTree(params.get("customIceServers"), Map.class) + .getAsJsonArray(); + } catch (Exception e) { + throw new Exception("Error in parameter 'customIceServersJson'. It is not a valid JSON object"); + } + } + if (customIceServersJsonArray != null) { + try { + 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()); + if (customIceServerJson.has("username")) { + iceServerPropertiesBuilder.username(customIceServerJson.get("username").getAsString()); + } + if (customIceServerJson.has("credential")) { + iceServerPropertiesBuilder.credential(customIceServerJson.get("credential").getAsString()); + } + IceServerProperties iceServerProperties = iceServerPropertiesBuilder.build(); + builder.addCustomIceServer(iceServerProperties); + } + } catch (Exception e) { + throw new Exception("Type error in some parameter of 'kurentoOptions': " + e.getMessage()); + } + } + // Build WEBRTC options builder.role(role).kurentoOptions(kurentoOptions); @@ -939,6 +960,8 @@ public class SessionRestController { .onlyPlayWithSubscribers(onlyPlayWithSubscribers).networkCache(networkCache).build(); } + + return builder; } From fca9c7b2ab637f3d7526b38af61b3881b3dc77b4 Mon Sep 17 00:00:00 2001 From: cruizba Date: Tue, 8 Feb 2022 20:04:51 +0100 Subject: [PATCH 03/11] Tests for IceServerProperties. Integrate new attribute to Connection and generation token logic --- openvidu-java-client/pom.xml | 5 + .../io/openvidu/java/client/Connection.java | 42 +-- .../java/client/ConnectionProperties.java | 14 +- .../java/client/IceServerProperties.java | 128 ++++++--- .../openvidu/server/core/SessionManager.java | 6 +- .../java/io/openvidu/server/core/Token.java | 24 +- .../openvidu/server/core/TokenGenerator.java | 14 +- .../server/rest/SessionRestController.java | 9 +- .../test/unit/IceServerPropertiesTests.java | 251 ++++++++++++++++++ 9 files changed, 417 insertions(+), 76 deletions(-) create mode 100644 openvidu-server/src/test/java/io/openvidu/server/test/unit/IceServerPropertiesTests.java 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); + } + +} From 784db2c83038fbe24264f22b48a9b7d023d6672e Mon Sep 17 00:00:00 2001 From: cruizba Date: Thu, 10 Feb 2022 20:30:28 +0100 Subject: [PATCH 04/11] Add Java client documentation --- .../java/client/ConnectionProperties.java | 40 +++++++++++++-- .../java/client/IceServerProperties.java | 51 ++++++++++++++++--- 2 files changed, 80 insertions(+), 11 deletions(-) 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 38903f42..a14e1607 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 @@ -43,14 +43,13 @@ public class ConnectionProperties { // WEBRTC private OpenViduRole role; private KurentoOptions kurentoOptions; + private List customIceServers = new ArrayList<>(); // IPCAM private String rtspUri; private Boolean adaptativeBitrate; private Boolean onlyPlayWithSubscribers; private Integer networkCache; - // External Turn Service - private List customIceServers = new ArrayList<>(); /** * Builder for {@link io.openvidu.java.client.ConnectionProperties}. @@ -231,7 +230,32 @@ public class ConnectionProperties { return this; } - // TODO: Comment + /** + * On certain type of networks, clients using default OpenVidu STUN/TURN server can not be reached it because + * firewall rules and network topologies at the client side. This method allows you to configure your + * own ICE Server for specific connections if you need it. This is usually not necessary, only it is usefull for + * OpenVidu users behind firewalls which allows traffic from/to specific ports which may need a custom + * ICE Server configuration + * + * Add an ICE Server if in your use case you need this connection to use your own ICE Server deployment. + * When the user uses this connection, it will use the specified ICE Servers defined here. + * + * The level of precedence for ICE Server configuration on every OpenVidu connection is: + *
    + *
  1. Configured ICE Server using Openvidu.setAdvancedCofiguration() at openvidu-browser.
  2. + *
  3. Configured ICE server at + * {@link io.openvidu.java.client.ConnectionProperties#customIceServers ConnectionProperties.customIceServers}
  4. + *
  5. Configured ICE Server at global configuration parameter: OPENVIDU_WEBRTC_ICE_SERVERS
  6. + *
  7. Default deployed Coturn within OpenVidu deployment
  8. + *
+ *
+ * If no value is found at level 1, level 2 will be used, and so on until level 4. + *
+ * This method is equivalent to level 2 of precedence. + *

+ * Only for + * {@link io.openvidu.java.client.ConnectionType#WEBRTC} + */ public Builder addCustomIceServer(IceServerProperties iceServerProperties) { this.customIceServers.add(iceServerProperties); return this; @@ -364,7 +388,15 @@ public class ConnectionProperties { return this.networkCache; } - // TODO: Comment + /** + * Returns a list of custom ICE Servers configured for this connection. + *

+ * See {@link io.openvidu.java.client.ConnectionProperties.Builder#addCustomIceServer(IceServerProperties)} for more + * information. + *

+ * Only for + * {@link io.openvidu.java.client.ConnectionType#WEBRTC} + */ public List getCustomIceServers() { return new ArrayList<>(this.customIceServers); } 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 565b6a6e..a478a1fa 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 @@ -10,20 +10,35 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; +/** + * See + * {@link io.openvidu.java.client.ConnectionProperties.Builder#addCustomIceServer(IceServerProperties)} + */ public class IceServerProperties { private String url; private String username; private String credential; + /** + * Returns the defined ICE Server url for this {@link IceServerProperties} object. + */ public String getUrl() { return url; } + /** + * Returns the Username to be used for TURN connections at the defined {@link IceServerProperties#getUrl()} + * and {@link IceServerProperties#getCredential()} for this {@link IceServerProperties} object. + */ public String getUsername() { return username; } + /** + * Returns the credential to be used for TURN connections at the defined {@link IceServerProperties#getUrl()} + * and {@link IceServerProperties#getUsername()} for this {@link IceServerProperties} object. + */ public String getCredential() { return credential; } @@ -35,9 +50,7 @@ public class IceServerProperties { } /** - * Ice server properties following RTCIceServers format: - * https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer/urls - * @return + * @hidden */ public JsonObject toJson() { JsonObject json = new JsonObject(); @@ -51,28 +64,56 @@ public class IceServerProperties { return json; } + /** + * Builder for {@link IceServerProperties} + */ public static class Builder { private String url; private String username; private String credential; + /** + * Set the url for the ICE Server you want to use. + * It should follow a valid format: + * + */ public IceServerProperties.Builder url(String url) { this.url = url; return this; } + /** + * Set a username for the ICE Server you want to use. + * This parameter should be defined only for TURN, not for STUN ICE Servers. + */ public IceServerProperties.Builder username(String userName) { this.username = userName; return this; } + /** + * Set a credential for the ICE Server you want to use. + * This parameter should be defined only for TURN, not for STUN ICE Servers. + */ public IceServerProperties.Builder credential(String credential) { this.credential = credential; return this; } + /** + * Builder for {@link io.openvidu.java.client.RecordingProperties} + * @throws IllegalArgumentException if the defined properties does not follows + * common STUN/TURN RFCs: + * + */ public IceServerProperties build() throws IllegalArgumentException { if (this.url == null) { throw new IllegalArgumentException("External turn url cannot be null"); @@ -91,10 +132,6 @@ public class IceServerProperties { return new IceServerProperties(this.url, this.username, this.credential); } - /** Parsing Turn Stun Uri based on: - * - https://datatracker.ietf.org/doc/html/rfc7065#section-3.1 - * - https://datatracker.ietf.org/doc/html/rfc7064#section-3.1 - */ private void checkValidStunTurn(String uri) throws IllegalArgumentException { final String TCP_TRANSPORT_SUFFIX = "?transport=tcp"; final String UDP_TRANSPORT_SUFFIX = "?transport=udp"; From 0437cc9199eb0bfa3c9b973a275bd228c10974b2 Mon Sep 17 00:00:00 2001 From: cruizba Date: Fri, 11 Feb 2022 20:03:26 +0100 Subject: [PATCH 05/11] Add customIceServers to openvidu-node-client. Send customIceServers to openvidu-browser in 'joinRoom' response --- .../client/internal/ProtocolElements.java | 1 + .../io/openvidu/java/client/Connection.java | 13 ++++++ .../java/client/ConnectionProperties.java | 2 +- .../java/client/IceServerProperties.java | 17 ++++++++ openvidu-node-client/src/Connection.ts | 14 ++++++- .../src/ConnectionProperties.ts | 27 ++++++++++++ .../src/IceServerProperties.ts | 42 +++++++++++++++++++ openvidu-node-client/src/Session.ts | 3 +- openvidu-node-client/src/index.ts | 3 +- .../server/core/SessionEventsHandler.java | 6 +++ .../java/io/openvidu/server/core/Token.java | 11 +++++ 11 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 openvidu-node-client/src/IceServerProperties.ts diff --git a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java index 85a327d5..9574b48c 100644 --- a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java +++ b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java @@ -165,6 +165,7 @@ public class ProtocolElements { public static final String PARTICIPANTJOINED_ROLE_PARAM = "role"; public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp"; public static final String PARTICIPANTJOINED_COTURNPORT_PARAM = "coturnPort"; + public static final String PARTICIPANTJOINED_CUSTOM_ICE_SERVERS = "customIceServers"; public static final String PARTICIPANTJOINED_TURNUSERNAME_PARAM = "turnUsername"; public static final String PARTICIPANTJOINED_TURNCREDENTIAL_PARAM = "turnCredential"; 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 3e7ba5dd..a833f3b9 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 @@ -192,6 +192,19 @@ public class Connection { return this.connectionProperties.getNetworkCache(); } + /** + * Returns a list of custom ICE Servers configured for this connection. + *

+ * See {@link io.openvidu.java.client.ConnectionProperties.Builder#addCustomIceServer(IceServerProperties)} for more + * information. + *

+ * Only for + * {@link io.openvidu.java.client.ConnectionType#WEBRTC} + */ + public List getCustomIceServers() { + return this.connectionProperties.getCustomIceServers(); + } + /** * Returns the token string associated to the Connection. This is the value that * must be sent to the client-side to be consumed in OpenVidu Browser method 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 a14e1607..a9125dec 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 @@ -243,7 +243,7 @@ public class ConnectionProperties { * The level of precedence for ICE Server configuration on every OpenVidu connection is: *
    *
  1. Configured ICE Server using Openvidu.setAdvancedCofiguration() at openvidu-browser.
  2. - *
  3. Configured ICE server at + *
  4. Configured ICE server at * {@link io.openvidu.java.client.ConnectionProperties#customIceServers ConnectionProperties.customIceServers}
  5. *
  6. Configured ICE Server at global configuration parameter: OPENVIDU_WEBRTC_ICE_SERVERS
  7. *
  8. Default deployed Coturn within OpenVidu deployment
  9. 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 a478a1fa..c9f72876 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,3 +1,20 @@ +/* + * (C) Copyright 2017-2020 OpenVidu (https://openvidu.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + package io.openvidu.java.client; import com.google.gson.JsonObject; diff --git a/openvidu-node-client/src/Connection.ts b/openvidu-node-client/src/Connection.ts index 35d8ba81..a54c36c0 100644 --- a/openvidu-node-client/src/Connection.ts +++ b/openvidu-node-client/src/Connection.ts @@ -18,6 +18,7 @@ import { Publisher } from './Publisher'; import { ConnectionProperties } from './ConnectionProperties'; import { OpenViduRole } from './OpenViduRole'; +import { IceServerProperties } from './IceServerProperties'; /** * See [[Session.connections]] @@ -138,6 +139,7 @@ export class Connection { this.connectionProperties.adaptativeBitrate = json.adaptativeBitrate; this.connectionProperties.onlyPlayWithSubscribers = json.onlyPlayWithSubscribers; this.connectionProperties.networkCache = json.networkCache; + this.connectionProperties.customIceServers = json.customIceServers ?? [] } else { this.connectionProperties = { type: json.type, @@ -148,7 +150,8 @@ export class Connection { rtspUri: json.rtspUri, adaptativeBitrate: json.adaptativeBitrate, onlyPlayWithSubscribers: json.onlyPlayWithSubscribers, - networkCache: json.networkCache + networkCache: json.networkCache, + customIceServers: json.customIceServers ?? [] } } this.role = json.role; @@ -224,6 +227,7 @@ export class Connection { this.connectionProperties.adaptativeBitrate === other.connectionProperties.adaptativeBitrate && this.connectionProperties.onlyPlayWithSubscribers === other.connectionProperties.onlyPlayWithSubscribers && this.connectionProperties.networkCache === other.connectionProperties.networkCache && + this.connectionProperties.customIceServers.length === other.connectionProperties.customIceServers.length && this.token === other.token && this.location === other.location && this.ip === other.ip && @@ -238,6 +242,14 @@ export class Connection { equals = (this.connectionProperties.kurentoOptions === other.connectionProperties.kurentoOptions); } } + if (equals) { + if (this.connectionProperties.customIceServers != null) { + const simpleIceComparator = (a: IceServerProperties, b: IceServerProperties) => (a.url > b.url) ? 1 : -1 + const sortedIceServers = this.connectionProperties.customIceServers.sort(simpleIceComparator); + const sortedOtherIceServers = other.connectionProperties.customIceServers.sort(simpleIceComparator); + equals = JSON.stringify(sortedIceServers) === JSON.stringify(sortedOtherIceServers); + } + } if (equals) { equals = JSON.stringify(this.subscribers.sort()) === JSON.stringify(other.subscribers.sort()); if (equals) { diff --git a/openvidu-node-client/src/ConnectionProperties.ts b/openvidu-node-client/src/ConnectionProperties.ts index ca88e962..77321c19 100644 --- a/openvidu-node-client/src/ConnectionProperties.ts +++ b/openvidu-node-client/src/ConnectionProperties.ts @@ -15,6 +15,7 @@ * */ +import { IceServerProperties } from './IceServerProperties'; import { ConnectionType } from './ConnectionType'; import { OpenViduRole } from './OpenViduRole'; @@ -127,4 +128,30 @@ export interface ConnectionProperties { */ networkCache?: number; + /** + * On certain type of networks, clients using default OpenVidu STUN/TURN server can not be reached it because + * firewall rules and network topologies at the client side. This method allows you to configure your + * own ICE Server for specific connections if you need it. This is usually not necessary, only it is usefull for + * OpenVidu users behind firewalls which allows traffic from/to specific ports which may need a custom + * ICE Server configuration + * + * Add an ICE Server if in your use case you need this connection to use your own ICE Server deployment. + * When the user uses this connection, it will use the specified ICE Servers defined here. + * + * The level of precedence for ICE Server configuration on every OpenVidu connection is: + * + * 1. Configured ICE Server using Openvidu.setAdvancedCofiguration() at openvidu-browser. + * 2. Configured ICE server at [[ConnectionProperties.customIceServers]]. + * 3. Configured ICE Server at global configuration parameter: `OPENVIDU_WEBRTC_ICE_SERVERS`. + * 4. Default deployed Coturn within OpenVidu deployment. + * + * + * If no value is found at level 1, level 2 will be used, and so on until level 4. + * + * This method is equivalent to level 2 of precedence. + * + * **Only for [[ConnectionType.WEBRTC]]** + * + */ + customIceServers?: IceServerProperties[]; } \ No newline at end of file diff --git a/openvidu-node-client/src/IceServerProperties.ts b/openvidu-node-client/src/IceServerProperties.ts new file mode 100644 index 00000000..3484ea04 --- /dev/null +++ b/openvidu-node-client/src/IceServerProperties.ts @@ -0,0 +1,42 @@ +/* + * (C) Copyright 2017-2020 OpenVidu (https://openvidu.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +export interface IceServerProperties { + + /** + * Set the url for the ICE Server you want to use. + * It should follow a valid format: + * + * - [https://datatracker.ietf.org/doc/html/rfc7065#section-3.1](https://datatracker.ietf.org/doc/html/rfc7065#section-3.1) + * - [https://datatracker.ietf.org/doc/html/rfc7064#section-3.1](https://datatracker.ietf.org/doc/html/rfc7064#section-3.1) + * + */ + url: string; + + /** + * Set a username for the ICE Server you want to use. + * This parameter should be defined only for TURN, not for STUN ICE Servers. + */ + username?: string; + + /** + * Set a credential for the ICE Server you want to use. + * This parameter should be defined only for TURN, not for STUN ICE Servers. + */ + credential?: string; + +} \ No newline at end of file diff --git a/openvidu-node-client/src/Session.ts b/openvidu-node-client/src/Session.ts index 9ca089a2..d006d179 100644 --- a/openvidu-node-client/src/Session.ts +++ b/openvidu-node-client/src/Session.ts @@ -150,7 +150,8 @@ export class Session { rtspUri: (!!connectionProperties && !!connectionProperties.rtspUri) ? connectionProperties.rtspUri : null, adaptativeBitrate: !!connectionProperties ? connectionProperties.adaptativeBitrate : null, onlyPlayWithSubscribers: !!connectionProperties ? connectionProperties.onlyPlayWithSubscribers : null, - networkCache: (!!connectionProperties && (connectionProperties.networkCache != null)) ? connectionProperties.networkCache : null + networkCache: (!!connectionProperties && (connectionProperties.networkCache != null)) ? connectionProperties.networkCache : null, + customIceServers: (!!connectionProperties && (!!connectionProperties.customIceServers != null)) ? connectionProperties.customIceServers : null }); axios.post( this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId + '/connection', diff --git a/openvidu-node-client/src/index.ts b/openvidu-node-client/src/index.ts index 669b4e90..80b7989c 100644 --- a/openvidu-node-client/src/index.ts +++ b/openvidu-node-client/src/index.ts @@ -12,4 +12,5 @@ export * from './Recording'; export * from './RecordingProperties'; export * from './Connection'; export * from './Publisher'; -export * from './VideoCodec'; \ No newline at end of file +export * from './VideoCodec'; +export * from './IceServerProperties'; \ No newline at end of file diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java b/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java index 27f675c0..bb9c592e 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import io.openvidu.java.client.IceServerProperties; import org.kurento.client.GenericMediaEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -186,6 +187,11 @@ public class SessionEventsHandler { } result.addProperty(ProtocolElements.PARTICIPANTJOINED_COTURNIP_PARAM, openviduConfig.getCoturnIp()); result.addProperty(ProtocolElements.PARTICIPANTJOINED_COTURNPORT_PARAM, openviduConfig.getCoturnPort()); + List customIceServers = participant.getToken().getCustomIceServers(); + if (customIceServers!= null && !customIceServers.isEmpty()) { + result.add(ProtocolElements.PARTICIPANTJOINED_CUSTOM_ICE_SERVERS, + participant.getToken().getCustomIceServersAsJson()); + } if (participant.getToken().getTurnCredentials() != null) { result.addProperty(ProtocolElements.PARTICIPANTJOINED_TURNUSERNAME_PARAM, participant.getToken().getTurnCredentials().getUsername()); 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 6633a761..a9dbe3bc 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,6 +17,7 @@ package io.openvidu.server.core; +import com.google.gson.JsonArray; import io.openvidu.java.client.*; import org.apache.commons.lang3.RandomStringUtils; @@ -143,6 +144,16 @@ public class Token { return json; } + public JsonArray getCustomIceServersAsJson() { + JsonArray customIceServersJsonList = new JsonArray(); + if (this.connectionProperties.getCustomIceServers() != null) { + this.connectionProperties.getCustomIceServers().forEach((customIceServer) -> { + customIceServersJsonList.add(customIceServer.toJson()); + }); + } + return customIceServersJsonList; + } + public JsonObject toJsonAsParticipant() { JsonObject json = new JsonObject(); json.addProperty("id", this.getConnectionId()); From 40ed2c5efc6fb442fb9c9f18b41db0c1dfb33f46 Mon Sep 17 00:00:00 2001 From: cruizba Date: Fri, 11 Feb 2022 22:38:51 +0100 Subject: [PATCH 06/11] openvidu-browser: Add cutomIceServers from 'joinRoom' rpc message --- openvidu-browser/src/OpenVidu/Session.ts | 14 ++++++++++++- .../Interfaces/Private/IceServerProperties.ts | 21 +++++++++++++++++++ .../Private/LocalConnectionOptions.ts | 2 ++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 openvidu-browser/src/OpenViduInternal/Interfaces/Private/IceServerProperties.ts diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index 94bef3fb..cdd4da7c 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -1510,7 +1510,19 @@ export class Session extends EventDispatcher { private processJoinRoomResponse(opts: LocalConnectionOptions) { this.sessionId = opts.session; - if (opts.coturnIp != null && opts.coturnPort != null && opts.turnUsername != null && opts.turnCredential != null) { + if (opts.customIceServers != null && opts.customIceServers.length > 0) { + this.openvidu.iceServers = []; + for(const iceServer of opts.customIceServers) { + let rtcIceServer: RTCIceServer = { + urls: [ iceServer.url ] + } + if (iceServer.username != null && iceServer.credential != null) { + rtcIceServer.username = iceServer.username; + rtcIceServer.credential = iceServer.credential; + } + this.openvidu.iceServers.push(rtcIceServer); + } + } else if (opts.coturnIp != null && opts.coturnPort != null && opts.turnUsername != null && opts.turnCredential != null) { const turnUrl1 = 'turn:' + opts.coturnIp + ':' + opts.coturnPort; this.openvidu.iceServers = [ { urls: [turnUrl1], username: opts.turnUsername, credential: opts.turnCredential } diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/IceServerProperties.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/IceServerProperties.ts new file mode 100644 index 00000000..38402e51 --- /dev/null +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/IceServerProperties.ts @@ -0,0 +1,21 @@ +/* + * (C) Copyright 2017-2022 OpenVidu (https://openvidu.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +export interface IceServerProperties { + url: string; + username?: string; + credential?: string; +} \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts index ad057f42..9f52796c 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts @@ -16,6 +16,7 @@ */ import { RemoteConnectionOptions } from './RemoteConnectionOptions'; +import { IceServerProperties } from './IceServerProperties'; export interface LocalConnectionOptions { id: string; @@ -35,4 +36,5 @@ export interface LocalConnectionOptions { mediaServer: string; videoSimulcast: boolean; life: number; + customIceServers?: IceServerProperties[] } From 4d579cf8b3180628153155831e1b626a9681a2b3 Mon Sep 17 00:00:00 2001 From: cruizba Date: Sun, 13 Feb 2022 19:33:41 +0100 Subject: [PATCH 07/11] openvidu: Add OPENVIDU_WEBRTC_ICE_SERVERS configuration paramater --- openvidu-browser/src/OpenVidu/Session.ts | 2 + .../server/config/OpenviduConfig.java | 53 ++++++++++++++++++- .../server/rest/SessionRestController.java | 5 ++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index cdd4da7c..5479400e 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -1516,9 +1516,11 @@ export class Session extends EventDispatcher { let rtcIceServer: RTCIceServer = { urls: [ iceServer.url ] } + logger.log("STUN/TURN server IP: " + iceServer.url); if (iceServer.username != null && iceServer.credential != null) { rtcIceServer.username = iceServer.username; rtcIceServer.credential = iceServer.credential; + logger.log('TURN credentials [' + iceServer.username + ':' + iceServer.credential + ']'); } this.openvidu.iceServers.push(rtcIceServer); } diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java index e4cccc2f..d143691c 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java @@ -38,6 +38,7 @@ import java.util.Map.Entry; import javax.annotation.PostConstruct; +import io.openvidu.java.client.IceServerProperties; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.DomainValidator; @@ -220,10 +221,12 @@ public class OpenviduConfig { private MediaServer mediaServerInfo = MediaServer.kurento; - // Media properties + // Webrtc properties private boolean webrtcSimulcast = false; + private List webrtcIceServers; + // Plain config properties getters public String getCoturnDatabaseDbname() { @@ -290,6 +293,10 @@ public class OpenviduConfig { return this.webrtcSimulcast; } + public List getWebrtcIceServers() { + return webrtcIceServers; + } + public String getOpenViduRecordingPath() { return this.openviduRecordingPath; } @@ -619,6 +626,8 @@ public class OpenviduConfig { checkCertificateType(); + webrtcIceServers = loadWebrtcIceServers("OPENVIDU_WEBRTC_ICE_SERVERS"); + } private void checkCertificateType() { @@ -1147,4 +1156,46 @@ public class OpenviduConfig { } } + private List loadWebrtcIceServers(String property) { + String rawIceServers = asOptionalString(property); + List webrtcIceServers = new ArrayList<>(); + if (rawIceServers == null || rawIceServers.isEmpty()) { + return webrtcIceServers; + } + List arrayIceServers = asJsonStringsArray(property); + for (String iceServerString : arrayIceServers) { + try { + IceServerProperties iceServerProperties = readIceServer(property, iceServerString); + webrtcIceServers.add(iceServerProperties); + } catch (Exception e) { + addError(property, iceServerString + " is not a valid webrtc ice server: " + e.getMessage()); + } + } + return webrtcIceServers; + } + + private IceServerProperties readIceServer(String property, String iceServerString) { + String url = null, username = null, credential = null; + String[] iceServerPropList = iceServerString.split(","); + for (String iceServerProp: iceServerPropList) { + String[] iceServerPropEntry = iceServerProp.split("="); + if (iceServerPropEntry.length == 2) { + if (iceServerProp.startsWith("url=")) { + url = iceServerPropEntry[1]; + } else if (iceServerProp.startsWith("username=")) { + username = iceServerPropEntry[1]; + } else if (iceServerProp.startsWith("credential=")) { + credential = iceServerPropEntry[1]; + } else { + addError(property, "Wrong parameter: " + iceServerProp); + } + } else { + addError(property, "Wrong parameter: " + iceServerProp); + } + } + IceServerProperties iceServerProperties = new IceServerProperties.Builder() + .url(url).username(username).credential(credential).build(); + return iceServerProperties; + } + } 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 1e3bc555..121ed0dd 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 @@ -934,6 +934,11 @@ public class SessionRestController { } catch (Exception e) { throw new Exception("Type error in some parameter of 'customIceServers': " + e.getMessage()); } + } else if(!openviduConfig.getWebrtcIceServers().isEmpty()){ + // If not defined in connection, check if defined in openvidu config + for (IceServerProperties iceServerProperties: openviduConfig.getWebrtcIceServers()) { + builder.addCustomIceServer(iceServerProperties); + } } // Build WEBRTC options From 5e196221983054bff2db689a3b8e45f189614ff0 Mon Sep 17 00:00:00 2001 From: cruizba Date: Sun, 13 Feb 2022 20:37:57 +0100 Subject: [PATCH 08/11] openvidu-testapp: Show configured ICE servers on created RTCPeerConnections to check ice server configuration from OpenVidu --- openvidu-testapp/src/app/app.module.ts | 3 ++ .../show-configured-ice.component.ts | 37 +++++++++++++++++++ .../app/components/video/video.component.html | 6 +++ .../app/components/video/video.component.ts | 11 ++++++ 4 files changed, 57 insertions(+) create mode 100644 openvidu-testapp/src/app/components/dialogs/show-configured-ice/show-configured-ice.component.ts diff --git a/openvidu-testapp/src/app/app.module.ts b/openvidu-testapp/src/app/app.module.ts index baf862d8..3de65373 100644 --- a/openvidu-testapp/src/app/app.module.ts +++ b/openvidu-testapp/src/app/app.module.ts @@ -33,6 +33,7 @@ import { OpenviduParamsService } from './services/openvidu-params.service'; import { TestFeedService } from './services/test-feed.service'; import { MuteSubscribersService } from './services/mute-subscribers.service'; import { SessionInfoDialogComponent } from "./components/dialogs/session-info-dialog/session-info-dialog.component"; +import { ShowIceServerConfiguredDialog } from './components/dialogs/show-configured-ice/show-configured-ice.component'; @NgModule({ declarations: [ @@ -53,6 +54,7 @@ import { SessionInfoDialogComponent } from "./components/dialogs/session-info-di ScenarioPropertiesDialogComponent, FilterDialogComponent, ShowCodecDialogComponent, + ShowIceServerConfiguredDialog, SessionInfoDialogComponent, UsersTableComponent, TableVideoComponent @@ -82,6 +84,7 @@ import { SessionInfoDialogComponent } from "./components/dialogs/session-info-di ScenarioPropertiesDialogComponent, FilterDialogComponent, ShowCodecDialogComponent, + ShowIceServerConfiguredDialog, SessionInfoDialogComponent ], bootstrap: [AppComponent] diff --git a/openvidu-testapp/src/app/components/dialogs/show-configured-ice/show-configured-ice.component.ts b/openvidu-testapp/src/app/components/dialogs/show-configured-ice/show-configured-ice.component.ts new file mode 100644 index 00000000..9123a930 --- /dev/null +++ b/openvidu-testapp/src/app/components/dialogs/show-configured-ice/show-configured-ice.component.ts @@ -0,0 +1,37 @@ +import { Component, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; + + +@Component({ + selector: 'app-ice-configured-dialog', + template: ` +
    +
      +
      +
    • +

      + ICE Server URL: {{iceServer.urls}} - + Username: {{iceServer.username}} - + Credential: {{iceServer.credential}} +

      +
    • +
      + +
    +
    + `, + styles: [` + #app-ice-configured-dialog-container { + text-align: center + } + `] +}) +export class ShowIceServerConfiguredDialog { + + iceServerList: RTCIceServer[] + + constructor(public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data) { + this.iceServerList = data.iceServerList; + } +} diff --git a/openvidu-testapp/src/app/components/video/video.component.html b/openvidu-testapp/src/app/components/video/video.component.html index aa59f1fd..7349a08f 100644 --- a/openvidu-testapp/src/app/components/video/video.component.html +++ b/openvidu-testapp/src/app/components/video/video.component.html @@ -11,6 +11,9 @@ +
    +
    -
    @@ -56,7 +56,7 @@ - From 285ff7b8f68128e3236fc67e6849bf731a5df472 Mon Sep 17 00:00:00 2001 From: cruizba Date: Wed, 16 Feb 2022 17:36:41 +0100 Subject: [PATCH 10/11] Fix wrong object in openvidu-java-client. Add e2e tests for customIceServers connection property in openvidu-java-client and openvidu-node-client --- .../io/openvidu/java/client/Connection.java | 6 +- openvidu-node-client/src/Connection.ts | 1 + openvidu-node-client/src/Session.ts | 12 +- .../test/e2e/OpenViduTestAppE2eTest.java | 260 ++++++++++++++++-- .../session-api-dialog.component.css | 9 + .../session-api-dialog.component.html | 21 ++ .../session-api-dialog.component.ts | 22 ++ .../app/components/video/video.component.html | 8 +- 8 files changed, 312 insertions(+), 27 deletions(-) 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 a833f3b9..8208c563 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 @@ -441,13 +441,13 @@ public class Connection { customIceServersJsonArray.forEach(iceJsonElem -> { JsonObject iceJsonObj = iceJsonElem.getAsJsonObject(); String url = (iceJsonObj.has("url") && !iceJsonObj.get("url").isJsonNull()) - ? json.get("url").getAsString() + ? iceJsonObj.get("url").getAsString() : null; String username = (iceJsonObj.has("username") && !iceJsonObj.get("username").isJsonNull()) - ? json.get("username").getAsString() + ? iceJsonObj.get("username").getAsString() : null; String credential = (iceJsonObj.has("credential") && !iceJsonObj.get("credential").isJsonNull()) - ? json.get("credential").getAsString() + ? iceJsonObj.get("credential").getAsString() : null; customIceServers.add(new IceServerProperties.Builder().url(url).username(username).credential(credential).build()); }); diff --git a/openvidu-node-client/src/Connection.ts b/openvidu-node-client/src/Connection.ts index a54c36c0..73fdf546 100644 --- a/openvidu-node-client/src/Connection.ts +++ b/openvidu-node-client/src/Connection.ts @@ -244,6 +244,7 @@ export class Connection { } if (equals) { if (this.connectionProperties.customIceServers != null) { + // Order alphabetically Ice servers using url just to keep the same list order. const simpleIceComparator = (a: IceServerProperties, b: IceServerProperties) => (a.url > b.url) ? 1 : -1 const sortedIceServers = this.connectionProperties.customIceServers.sort(simpleIceComparator); const sortedOtherIceServers = other.connectionProperties.customIceServers.sort(simpleIceComparator); diff --git a/openvidu-node-client/src/Session.ts b/openvidu-node-client/src/Session.ts index d006d179..9b98473b 100644 --- a/openvidu-node-client/src/Session.ts +++ b/openvidu-node-client/src/Session.ts @@ -28,6 +28,7 @@ import { RecordingMode } from './RecordingMode'; import { SessionProperties } from './SessionProperties'; import { TokenOptions } from './TokenOptions'; import { RecordingProperties } from 'RecordingProperties'; +import { IceServerProperties } from 'IceServerProperties'; export class Session { @@ -561,7 +562,6 @@ export class Session { // 1. Array to store fetched connections and later remove closed ones const fetchedConnectionIds: string[] = []; json.connections.content.forEach(jsonConnection => { - const connectionObj: Connection = new Connection(jsonConnection); fetchedConnectionIds.push(connectionObj.connectionId); let storedConnection = this.connections.find(c => c.connectionId === connectionObj.connectionId); @@ -584,6 +584,16 @@ export class Session { // Order connections by time of creation this.connections.sort((c1, c2) => (c1.createdAt > c2.createdAt) ? 1 : ((c2.createdAt > c1.createdAt) ? -1 : 0)); + + // Order Ice candidates in connection properties + this.connections.forEach(connection => { + if (connection.connectionProperties.customIceServers != null && + connection.connectionProperties.customIceServers.length > 0) { + // Order alphabetically Ice servers using url just to keep the same list order. + const simpleIceComparator = (a: IceServerProperties, b: IceServerProperties) => (a.url > b.url) ? 1 : -1 + connection.connectionProperties.customIceServers.sort(simpleIceComparator); + } + }); // Populate activeConnections array this.updateActiveConnectionsArray(); return this; diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java index 869b2f6b..4bc24145 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java @@ -35,6 +35,8 @@ import java.util.concurrent.TimeoutException; import java.util.function.BiFunction; import java.util.stream.Collectors; +import com.google.gson.*; +import io.openvidu.java.client.*; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.jupiter.api.BeforeAll; @@ -49,35 +51,15 @@ import org.openqa.selenium.Dimension; import org.openqa.selenium.Keys; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.ExpectedConditions; import org.springframework.test.context.junit.jupiter.SpringExtension; -import com.google.gson.JsonArray; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.mashape.unirest.http.HttpMethod; import io.appium.java_client.AppiumDriver; -import io.openvidu.java.client.Connection; -import io.openvidu.java.client.ConnectionProperties; -import io.openvidu.java.client.ConnectionType; -import io.openvidu.java.client.KurentoOptions; -import io.openvidu.java.client.MediaMode; -import io.openvidu.java.client.OpenVidu; -import io.openvidu.java.client.OpenViduHttpException; -import io.openvidu.java.client.OpenViduJavaClientException; -import io.openvidu.java.client.OpenViduRole; -import io.openvidu.java.client.Publisher; -import io.openvidu.java.client.Recording; import io.openvidu.java.client.Recording.OutputMode; -import io.openvidu.java.client.RecordingLayout; -import io.openvidu.java.client.RecordingMode; -import io.openvidu.java.client.RecordingProperties; -import io.openvidu.java.client.Session; -import io.openvidu.java.client.SessionProperties; -import io.openvidu.java.client.VideoCodec; import io.openvidu.test.browsers.BrowserUser; import io.openvidu.test.browsers.utils.BrowserNames; import io.openvidu.test.browsers.utils.CustomHttpClient; @@ -3002,6 +2984,20 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { body = "{'type':'WEBRTC','role':'MODERATOR','data':true}"; restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body, HttpStatus.SC_BAD_REQUEST); + + // 400 - Test some not valid customIceServers configured + body = "{'customIceServers': [{'url':'bad-ice-server'}]}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body, + HttpStatus.SC_BAD_REQUEST); + + body = "{'customIceServers': [{'url':'turn:bad-ice-server'}]}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body, + HttpStatus.SC_BAD_REQUEST); + + body = "{'customIceServers': [{'url':'bad-prefix:bad-ice-server'}]}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body, + HttpStatus.SC_BAD_REQUEST); + // 200 String kurentoOpts = "'kurentoOptions':{'videoMaxSendBandwidth':777,'allowedFilters':['GStreamerFilter']}"; body = "{'type':'WEBRTC','role':'MODERATOR','data':'SERVER_DATA'," + kurentoOpts + "}"; @@ -3011,6 +3007,18 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { restClient.rest(HttpMethod.DELETE, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection/" + res.get("id").getAsString(), HttpStatus.SC_NO_CONTENT); + + // 200 - Test some good Ice Servers configured + String goodTurn = "{'url': 'turn:valid-domain.es', 'username': 'user', 'credential': 'pass'}"; + String goodStun = "{'url': 'stun:valid-domain.es:1234'}"; + body = "{ 'customIceServers': [" + goodTurn + "," + goodStun + "]}"; + res = restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body, + HttpStatus.SC_OK, true, false, true, + mergeJson(DEFAULT_JSON_PENDING_CONNECTION, "{ 'customIceServers': [" + goodTurn + "," + goodStun+ "] }", new String[0])); + restClient.rest(HttpMethod.DELETE, + "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection/" + res.get("id").getAsString(), + HttpStatus.SC_NO_CONTENT); + // Default values res = restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", "{}", HttpStatus.SC_OK); @@ -4374,6 +4382,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assert.assertTrue("Wrong token Connection property", connectionJson.get("token").getAsString().contains(session.getSessionId())); Assert.assertEquals("Wrong number of keys in connectionProperties", 10, connectionProperties.keySet().size()); + Assert.assertTrue("Wrong customIceServer property", connectionProperties.get("customIceServers").getAsJsonArray().size() == 0); Assert.assertEquals("Wrong type property", ConnectionType.WEBRTC.name(), connectionProperties.get("type").getAsString()); Assert.assertEquals("Wrong data property", "MY_SERVER_DATA", connectionProperties.get("data").getAsString()); @@ -4489,7 +4498,216 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assert.assertFalse("Java fetch should be false", OV.fetch()); checkNodeFetchChanged(user, false, true); checkNodeFetchChanged(user, false, false); + } + + @Test + @DisplayName("Custom Ice Server connection tests from openvidu-java-client") + void customIceServerConnectionJavaClientTest() throws Exception { + customIceServerTest("openvidu-java-client"); + } + + @Test + @DisplayName("Custom Ice Server connection tests from openvidu-node-client") + void customIceServerConnectionNodeClientTest() throws Exception { + customIceServerTest("openvidu-node-client"); + } + + private void customIceServerTest(String client) throws Exception { + OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); + + // ------ + // 1. Initialize connection with Custom Ice Servers from openvidu-java-client + // ------ + Session session = OV.createSession(); + String sessionId1 = session.getSessionId(); + + Assert.assertFalse("Java fetch should be false", OV.fetch()); + Assert.assertFalse("Session fetch should be false", session.fetch()); + user.getDriver().findElement(By.id("add-user-btn")).click(); + WebElement sessionName1 = user.getDriver().findElement(By.id("session-name-input-0")); + sessionName1.clear(); + sessionName1.sendKeys(session.getSessionId()); + user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER)); + user.getEventManager().waitUntilEventReaches("connectionCreated", 1); + user.getEventManager().waitUntilEventReaches("accessAllowed", 1); + user.getEventManager().waitUntilEventReaches("streamCreated", 1); + user.getEventManager().waitUntilEventReaches("streamPlaying", 1); + + session.fetch(); + Assert.assertEquals(session.getActiveConnections().size(), 1); + + // Add second user with connection using custom ice servers + Connection connection1 = createConnWithCustomIceServer(session, user, client); + user.getDriver().findElement(By.id("add-user-btn")).click(); + WebElement sessionName2 = user.getDriver().findElement(By.id("session-name-input-1")); + sessionName2.clear(); + sessionName2.sendKeys(sessionId1); + + user.getDriver().findElement(By.id("session-settings-btn-1")).click(); + Thread.sleep(1000); + + WebElement token1Input = user.getDriver().findElement(By.cssSelector("#custom-token-div input")); + token1Input.clear(); + token1Input.sendKeys(connection1.getToken()); + + user.getDriver().findElement(By.id("save-btn")).click(); + Thread.sleep(1000); + user.getDriver().findElements(By.className("join-btn")) + .stream().filter(el -> el.isEnabled()).forEach(el -> el.sendKeys(Keys.ENTER)); + user.getEventManager().waitUntilEventReaches("connectionCreated", 4); + user.getEventManager().waitUntilEventReaches("accessAllowed", 2); + user.getEventManager().waitUntilEventReaches("streamCreated", 4); + user.getEventManager().waitUntilEventReaches("streamPlaying", 4); + + // ------ + // 2. Check that the IceServer is correctly setup on RTCPeerConnections created. This will ensure that the property + // is reached in WebRTC browser related objects + // ------ + List iceConfiguredButtons = user.getDriver().findElements(By.className("ice-config-button-" + connection1.getConnectionId())); + for (WebElement iceConfigured : iceConfiguredButtons) { + iceConfigured.click(); + Thread.sleep(1000); + for(int i = 0; i < connection1.getCustomIceServers().size(); i++) { + IceServerProperties customIceServer = connection1.getCustomIceServers().get(i); + String foundIceUrl = user.getDriver().findElement(By.id("ice-server-url-" + i)).getText(); + String foundIceUsername = null, foundIceCred = null; + List foundIceUsernameWebElem = user.getDriver().findElements(By.id("ice-server-username-" + i)); + List foundIceCredWebElem = user.getDriver().findElements(By.id("ice-server-credential-" + i)); + if (foundIceUsernameWebElem.size() == 1) { + foundIceUsername = foundIceUsernameWebElem.get(0).getText(); + } + if (foundIceCredWebElem.size() == 1) { + foundIceCred = foundIceCredWebElem.get(0).getText(); + } + Assert.assertEquals(foundIceUrl, customIceServer.getUrl()); + Assert.assertEquals(foundIceUsername, customIceServer.getUsername()); + Assert.assertEquals(foundIceCred, customIceServer.getCredential()); + } + user.getDriver().findElement(By.id("close-dialog-btn")).click(); + Thread.sleep(1000); + } + + // ------ + // 3. Check that the data in openvidu-node-client is correctly fetched + // ------ + user.getDriver().findElement(By.id("session-api-btn-0")).click(); + Thread.sleep(1000); + checkNodeFetchChanged(user, false, true); + checkNodeFetchChanged(user, false, false); checkNodeFetchChanged(user, true, false); + + // ------ + // 4. Check if Ice Servers are correctly received in openvidu-node-client + // ------ + user.getDriver().findElement(By.id("close-dialog-btn")).click(); + Thread.sleep(1000); + user.getDriver().findElement(By.id("session-info-btn-0")).click(); + + // 4.1. Get all session data + JsonObject res = JsonParser + .parseString(user.getDriver().findElement(By.id("session-text-area")).getAttribute("value")) + .getAsJsonObject(); + + JsonArray connectionsJsonArray = res.get("connections").getAsJsonArray(); + boolean foundConnection = false; + for (JsonElement connectionJson: connectionsJsonArray) { + // 4.2. Of all connections, get only the one created with openvidu-java-client with customIceServers added + // Check if connection is the one configured from java client + String connectionId = connectionJson.getAsJsonObject().get("connectionId").getAsString(); + if (connectionId.equals(connection1.getConnectionId())) { + // 4.3. If connection with custom ice server is found, get the property with all customIceServers + foundConnection = true; + JsonArray customIceServersJsonArray = connectionJson.getAsJsonObject() + .get("connectionProperties").getAsJsonObject() + .get("customIceServers").getAsJsonArray(); + for (IceServerProperties customIceServer: connection1.getCustomIceServers()) { + // 4.4 Compare both list, the one created with openvidu-java-client with the one received from openvidu-node-client + boolean foundIceServer = false; + Iterator customIceServersJsonIterator = customIceServersJsonArray.iterator(); + while(customIceServersJsonIterator.hasNext() && !foundIceServer) { + // 4.5 When the custom ICE server is found in the openvidu-node-client object, compare it with the + // openvidu-java-client object + JsonObject customIceJsonObject = customIceServersJsonIterator.next().getAsJsonObject(); + String url = customIceJsonObject.get("url").getAsString(); + if (url.equals(customIceServer.getUrl())) { + foundIceServer = true; + Assert.assertEquals(customIceServer.getUrl(), customIceJsonObject.get("url").getAsString()); + if (customIceJsonObject.get("username") != null) { + Assert.assertEquals(customIceServer.getUsername(), customIceJsonObject.get("username").getAsString()); + } + if (customIceJsonObject.get("credential") != null) { + Assert.assertEquals(customIceServer.getCredential(), customIceJsonObject.get("credential").getAsString()); + } + } + } + // 4.6 Assert that the custom Ice Server was found on the openvidu-node-client connection object + Assert.assertTrue(foundIceServer); + } + } + + } + // 4.7 Assert that the connection was found on the openvidu-node-client session object, to fail in case it was not registered + Assert.assertTrue(foundConnection); + } + + private Connection createConnWithCustomIceServer(Session session, OpenViduTestappUser user, String fromClient) + throws OpenViduJavaClientException, OpenViduHttpException, InterruptedException { + if (fromClient.equals("openvidu-java-client")) { + IceServerProperties iceServerProperties1 = new IceServerProperties.Builder() + .url("turn:turn-server.com") + .username("usertest") + .credential("credtest") + .build(); + IceServerProperties iceServerProperties2 = new IceServerProperties.Builder() + .url("stun:1.2.3.4:1234") + .build(); + ConnectionProperties connectionProperties = new ConnectionProperties.Builder() + .addCustomIceServer(iceServerProperties1) + .addCustomIceServer(iceServerProperties2) + .build(); + return session.createConnection(connectionProperties); + } else if (fromClient.equals("openvidu-node-client")) { + user.getDriver().findElement(By.id("session-api-btn-0")).click(); + Thread.sleep(1000); + user.getDriver().findElement(By.id("num-ice-servers-select")).click(); + Thread.sleep(500); + user.getDriver().findElement(By.id("num-ice-servers-2")).click(); + Thread.sleep(500); + WebElement iceUrl1 = user.getDriver().findElement(By.id("ice-server-url-0")); + WebElement iceUsername1 = user.getDriver().findElement(By.id("ice-server-username-0")); + WebElement iceCredential1 = user.getDriver().findElement(By.id("ice-server-credential-0")); + WebElement iceUrl2 = user.getDriver().findElement(By.id("ice-server-url-1")); + iceUrl1.clear(); + iceUsername1.clear(); + iceCredential1.clear(); + iceUrl2.clear(); + iceUrl1.sendKeys("turn:turn-server.com"); + iceUsername1.sendKeys("usertest"); + iceCredential1.sendKeys("credtest"); + iceUrl2.sendKeys("stun:1.2.3.4:1234"); + + // Create connection + user.getDriver().findElement(By.id("crate-connection-api-btn")).click(); + Thread.sleep(1000); + String responseAreaText = user.getDriver().findElement(By.id("api-response-text-area")).getAttribute("value"); + user.getDriver().findElement(By.id("close-dialog-btn")).click(); + String connectionCreatedPrefix = "Connection created: "; + Assert.assertTrue(responseAreaText.startsWith(connectionCreatedPrefix)); + String connectionStringResponse = responseAreaText.split(connectionCreatedPrefix, 2)[1]; + JsonObject connectionJsonResponse = JsonParser.parseString(connectionStringResponse).getAsJsonObject(); + String connectionId = connectionJsonResponse.get("connectionId").getAsString(); + Assert.assertTrue(session.fetch()); + Assert.assertFalse(session.fetch()); + Assert.assertFalse(OV.fetch()); + for (Connection connection: session.getConnections()) { + if (connection.getConnectionId().equals(connectionId)) { + return connection; + } + } + return null; + } else { + return null; + } } @Test diff --git a/openvidu-testapp/src/app/components/dialogs/session-api-dialog/session-api-dialog.component.css b/openvidu-testapp/src/app/components/dialogs/session-api-dialog/session-api-dialog.component.css index 0cb6e229..019f895f 100644 --- a/openvidu-testapp/src/app/components/dialogs/session-api-dialog/session-api-dialog.component.css +++ b/openvidu-testapp/src/app/components/dialogs/session-api-dialog/session-api-dialog.component.css @@ -39,3 +39,12 @@ mat-dialog-content button { top: 0; right: 0; } + +#manual-turn-div { + background-color: #f7f7f7; + margin-top: 10px; + margin-bottom: 10px; + padding: 5px; + border: 1px solid #00000026; + border-radius: 3px; +} diff --git a/openvidu-testapp/src/app/components/dialogs/session-api-dialog/session-api-dialog.component.html b/openvidu-testapp/src/app/components/dialogs/session-api-dialog/session-api-dialog.component.html index f3083bad..7698dbe2 100644 --- a/openvidu-testapp/src/app/components/dialogs/session-api-dialog/session-api-dialog.component.html +++ b/openvidu-testapp/src/app/components/dialogs/session-api-dialog/session-api-dialog.component.html @@ -30,6 +30,27 @@ +
    + + Custom Ice Servers + + + {{ i }} + + + +
    + + + + + + + + + +
    +
    -
    @@ -56,7 +58,9 @@ - From 2719540d32b95ba0a199184a9bbc18fd811a2c41 Mon Sep 17 00:00:00 2001 From: cruizba Date: Wed, 16 Feb 2022 18:11:53 +0100 Subject: [PATCH 11/11] openvidu: Rename IceServerPropertiesTests to IceServerPropertiesTest --- ...erverPropertiesTests.java => IceServerPropertiesTest.java} | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename openvidu-server/src/test/java/io/openvidu/server/test/unit/{IceServerPropertiesTests.java => IceServerPropertiesTest.java} (99%) 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/IceServerPropertiesTest.java similarity index 99% rename from openvidu-server/src/test/java/io/openvidu/server/test/unit/IceServerPropertiesTests.java rename to openvidu-server/src/test/java/io/openvidu/server/test/unit/IceServerPropertiesTest.java index b3d8db1f..729b2f10 100644 --- a/openvidu-server/src/test/java/io/openvidu/server/test/unit/IceServerPropertiesTests.java +++ b/openvidu-server/src/test/java/io/openvidu/server/test/unit/IceServerPropertiesTest.java @@ -4,11 +4,9 @@ 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 { +public class IceServerPropertiesTest { @Test @DisplayName("IceServerProperty exceptions tests")