Базовый SFU SDK для g365sfu
This commit is contained in:
77
src/main/java/io/g365sfu/SFU.java
Normal file
77
src/main/java/io/g365sfu/SFU.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package io.g365sfu;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import io.g365sfu.exception.SFUException;
|
||||
import io.g365sfu.exception.SFUHandshakeException;
|
||||
import io.g365sfu.net.SfuSock;
|
||||
|
||||
|
||||
public class SFU {
|
||||
|
||||
private String serverAddress;
|
||||
private String secretKey;
|
||||
private SfuSock socket;
|
||||
|
||||
/**
|
||||
* Конструктор для создания объекта SFU, который будет использоваться для установления соединения с SFU сервером.
|
||||
* @param serverAddress адрес SFU сервера в формате "host:port", например "sfu.example.com:8080"
|
||||
* @param secretKey секретный ключ для аутентификации с SFU сервером, который должен быть согласован с настройками сервера.
|
||||
*/
|
||||
public SFU(String serverAddress, String secretKey) {
|
||||
this.serverAddress = serverAddress;
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить соединение с SFU сервером и начать обмен рукопожатиями для аутентификации и установления безопасного канала связи.
|
||||
* @throws URISyntaxException если адрес сервера имеет неправильный формат
|
||||
* @throws InterruptedException если соединение было прервано во время попытки подключения
|
||||
* @throws SFUException если не удалось установить соединение с SFU сервером или если соединение было установлено,
|
||||
* но не открыто после подключения
|
||||
* @throws TimeoutException не удалось обменяться рукопожатиями с SFU сервером в течение 30 секунд
|
||||
* @throws ExecutionException если во время обмена рукопожатиями произошла ошибка выполнения
|
||||
* @throws SFUHandshakeException если обмен рукопожатиями с SFU завершился неудачно (например, плохой ключ)
|
||||
*/
|
||||
public void connect() throws URISyntaxException, InterruptedException, SFUException, ExecutionException, TimeoutException, SFUHandshakeException {
|
||||
this.socket = new SfuSock(this.serverAddress);
|
||||
boolean connected = this.socket.connectBlocking(30, TimeUnit.SECONDS);
|
||||
if(!connected){
|
||||
throw new SFUException("Failed to connect to SFU server, timeout after 30 seconds: " + this.serverAddress);
|
||||
}
|
||||
if(!this.socket.isOpen()) {
|
||||
throw new SFUException("Connection to SFU server at " + this.serverAddress + " is not open");
|
||||
}
|
||||
boolean estabilished = this.socket.handshakeExchange(this.secretKey).get(30, TimeUnit.SECONDS);
|
||||
if(!estabilished) {
|
||||
throw new SFUHandshakeException("Failed to establish handshake with SFU server at " + this.serverAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить адрес SFU сервера, к которому установлено соединение
|
||||
* @return адрес SFU сервера
|
||||
*/
|
||||
public String getServerAddress() {
|
||||
return this.serverAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить соединение к SFU серверу, если оно было установлено
|
||||
* @return объект SfuSock, представляющий соединение к SFU серверу
|
||||
*/
|
||||
public SfuSock getConnection() {
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, установлено ли соединение с SFU сервером и открыто ли оно
|
||||
* @return true, если соединение установлено и открыто, false в противном случае
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return this.socket != null && this.socket.isOpen();
|
||||
}
|
||||
}
|
||||
13
src/main/java/io/g365sfu/exception/SFUException.java
Normal file
13
src/main/java/io/g365sfu/exception/SFUException.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package io.g365sfu.exception;
|
||||
|
||||
public class SFUException extends Exception {
|
||||
|
||||
public SFUException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SFUException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.g365sfu.exception;
|
||||
|
||||
public class SFUHandshakeException extends Exception {
|
||||
|
||||
public SFUHandshakeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SFUHandshakeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
90
src/main/java/io/g365sfu/net/SfuSock.java
Normal file
90
src/main/java/io/g365sfu/net/SfuSock.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package io.g365sfu.net;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
public class SfuSock extends WebSocketClient {
|
||||
|
||||
private CompletableFuture<Boolean> handshakeFuture = new CompletableFuture<>();
|
||||
|
||||
public SfuSock(String serverAddress) throws URISyntaxException {
|
||||
super(new URI("ws://" + serverAddress));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake handshakedata) {
|
||||
System.out.println("Connected to SFU server");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteBuffer bytes) {
|
||||
if(bytes.remaining() < 1) {
|
||||
System.err.println("Received empty message from SFU server");
|
||||
return;
|
||||
}
|
||||
byte messageType = bytes.get();
|
||||
if(messageType == 0x01) {
|
||||
/**
|
||||
* Сервер ответил на рукопожатие, и мы можем считать его успешным
|
||||
*/
|
||||
this.handshakeFuture.complete(true);
|
||||
return;
|
||||
}
|
||||
if(messageType == 0xFF) {
|
||||
/**
|
||||
* Сервер отклонил рукопожатие, и мы должны считать его неудачным
|
||||
*/
|
||||
this.handshakeFuture.complete(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
System.err.println("Received unexpected text message from SFU server: " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
if(!this.handshakeFuture.isDone()) {
|
||||
/**
|
||||
* Если соединение было закрыто до завершения рукопожатия, то мы считаем его неудачным
|
||||
*/
|
||||
this.handshakeFuture.complete(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex){
|
||||
System.err.println("Error: " + ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Запускает обмен рукопожатиями с сервером SFU
|
||||
* @param secretKey секретный ключ для аутентификации с сервером SFU
|
||||
* @return CompletableFuture, который будет завершен с результатом true, если
|
||||
* рукопожатие прошло успешно, или false, если рукопожатие не удалось или было отклонено сервером SFU
|
||||
* @internal Этот метод отправляет пакет рукопожатия, который состоит из одного байта 0x01, за которым следует секретный ключ в виде строки байтов.
|
||||
* Сервер SFU должен ответить одним байтом 0x01 для успешного рукопожатия или 0xFF для отклонения рукопожатия.
|
||||
*/
|
||||
public CompletableFuture<Boolean> handshakeExchange(String secretKey) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(secretKey.length() + 1);
|
||||
/**
|
||||
* 0x01 - код рукопожатия в соотвествии с протоколом g365sfu, за которым следует секретный ключ в виде строки байтов
|
||||
*/
|
||||
buffer.put((byte)0x01);
|
||||
buffer.put(secretKey.getBytes());
|
||||
buffer.flip();
|
||||
/**
|
||||
* Отправляем сформированный пакет, и возвращаем CompletableFuture
|
||||
*/
|
||||
this.send(buffer);
|
||||
return this.handshakeFuture;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user