Skip to content

Commit

Permalink
Initial logic in openvidu-java-client to add to the ConnectionPropert…
Browse files Browse the repository at this point in the history
…ies class a new 'customIceServers' parameter
  • Loading branch information
cruizba committed Feb 2, 2022
1 parent e56ac82 commit d7eae78
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
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()}
Expand Down Expand Up @@ -428,8 +430,28 @@ protected Connection resetWithJson(JsonObject json) {
Integer networkCache = (json.has("networkCache") && !json.get("networkCache").isJsonNull())
? json.get("networkCache").getAsInt()
: null;

// External Ice Servers
List<IceServerProperties> customIceServers = new ArrayList<>();
if (json.has("customIceServers") && json.get("customIceServers").isJsonArray()) {
JsonArray customIceServersJsonArray = json.get("customIceServers").getAsJsonArray();
customIceServersJsonArray.forEach(iceJsonElem -> {
JsonObject iceJsonObj = iceJsonElem.getAsJsonObject();
String url = (iceJsonObj.has("urls") && !iceJsonObj.get("urls").isJsonNull())
? json.get("urls").getAsString()
: null;
String username = (iceJsonObj.has("username") && !iceJsonObj.get("username").isJsonNull())
? json.get("username").getAsString()
: null;
String credential = (iceJsonObj.has("credential") && !iceJsonObj.get("credential").isJsonNull())
? json.get("credential").getAsString()
: null;
customIceServers.add(new IceServerProperties.Builder().url(url).username(username).credential(credential).build());
});
}

this.connectionProperties = new ConnectionProperties(type, data, record, role, null, rtspUri, adaptativeBitrate,
onlyPlayWithSubscribers, networkCache);
onlyPlayWithSubscribers, networkCache, customIceServers);

return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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)}
Expand All @@ -22,6 +26,9 @@ public class ConnectionProperties {
private Boolean onlyPlayWithSubscribers;
private Integer networkCache;

// External Turn Service
private List<IceServerProperties> customIceServers;

/**
*
* Builder for {@link io.openvidu.java.client.ConnectionProperties}
Expand All @@ -42,12 +49,16 @@ public static class Builder {
private Boolean onlyPlayWithSubscribers;
private Integer networkCache;

// External Turn Service
private List<IceServerProperties> 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);
}

/**
Expand Down Expand Up @@ -219,11 +230,17 @@ public Builder networkCache(int networkCache) {
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<IceServerProperties> customIceServers) {
this.type = type;
this.data = data;
this.record = record;
Expand All @@ -233,6 +250,7 @@ public Builder networkCache(int networkCache) {
this.adaptativeBitrate = adaptativeBitrate;
this.onlyPlayWithSubscribers = onlyPlayWithSubscribers;
this.networkCache = networkCache;
this.customIceServers = customIceServers;
}

/**
Expand Down Expand Up @@ -346,6 +364,11 @@ public Integer getNetworkCache() {
return this.networkCache;
}

// TODO: Comment
public List<IceServerProperties> getCustomIceServers() {
return new ArrayList<>(this.customIceServers);
}

public JsonObject toJson(String sessionId) {
JsonObject json = new JsonObject();
json.addProperty("session", sessionId);
Expand Down Expand Up @@ -397,6 +420,14 @@ public JsonObject toJson(String sessionId) {
} 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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> TURN_PROTOCOLS = new HashSet<>(Arrays.asList(
"turn",
"turns"
));
final Set<String> 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");
}
}
}
}

}

0 comments on commit d7eae78

Please sign in to comment.