Инициализация протокола Rosetta

This commit is contained in:
RoyceDa
2026-02-02 02:08:53 +02:00
parent 782a5b1c9b
commit c1b4ca5db7
17 changed files with 421 additions and 26 deletions

View File

@@ -0,0 +1,42 @@
package com.rosetta.im;
import com.rosetta.im.executors.Executor0Handshake;
import com.rosetta.im.packet.Packet0Handshake;
import com.rosetta.im.protocol.packet.PacketManager;
public class Boot {
private PacketManager packetManager;
public Boot(PacketManager packetManager) {
this.packetManager = packetManager;
}
public PacketManager getPacketManager() {
return this.packetManager;
}
/**
* Инициализация всех пакетов и их обработчиков
*/
public void bootstrap() {
this.registerAllPackets();
this.registerAllExecutors();
this.printBootMessage();
}
private void registerAllPackets() {
this.packetManager.registerPacket(0, Packet0Handshake.class);
}
private void registerAllExecutors() {
this.packetManager.registerExecutor(0, Executor0Handshake.class);
}
private void printBootMessage() {
System.out.println("Bootstrapping completed. All packets and executors are registered.");
System.out.println("Total packets registered: " + this.packetManager.totalPackets());
System.out.println("Total executors registered: " + this.packetManager.totalExecutors());
}
}

View File

@@ -1,20 +1,28 @@
package com.rosetta.im;
import com.rosetta.im.packet.Packet0Handshake;
import com.rosetta.im.protocol.Server;
import com.rosetta.im.protocol.Settings;
import com.rosetta.im.protocol.packet.PacketManager;
public class Main {
public static void main(String[] args) {
PacketManager manager = new PacketManager();
manager.registerPacket(0, Packet0Handshake.class);
/**
* Регистрация всех пакетов и их обработчиков
*/
Boot boot = new Boot(manager);
boot.bootstrap();
Server server = new Server(8881, manager);
/**
* Загрузка настроек сервера
*/
Settings settings = new Settings(8881, 30);
/**
* Запуск сервера на порту 8881
*/
Server server = new Server(settings, manager);
server.start();
System.out.println("Rosetta server started...");
}
}

View File

@@ -1,13 +1,18 @@
package com.rosetta.im.executors;
import com.rosetta.im.packet.Packet0Handshake;
import com.rosetta.im.protocol.Client;
import com.rosetta.im.protocol.packet.Packet;
import com.rosetta.im.protocol.packet.PacketExecutor;
public class Executor0Handshake implements PacketExecutor {
@Override
public void onPacketReceived(Class<? extends Packet> packet) {
public void onPacketReceived(Packet packet, Client client) {
Packet0Handshake handshake = (Packet0Handshake) packet;
String publicKey = handshake.getPublicKey();
String privateKey = handshake.getPrivateKey();
int b = 0;
}
}

View File

@@ -26,6 +26,11 @@ public class Packet0Handshake extends Packet {
* Публичный и приватный ключи клиента
*/
private String publicKey;
/**
* Приватный ключ клиента
* Это не совсем приватный ключ, а лишь необратимо зашифрованная его версия
* для идентификации клиента на сервере.
*/
private String privateKey;
/**
* Версия протокола клиента
@@ -80,4 +85,52 @@ public class Packet0Handshake extends Packet {
);
}
public String getPublicKey() {
return publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public int getProtocolVersion() {
return protocolVersion;
}
public int getHeartbeatInterval() {
return heartbeatInterval;
}
public Device getDevice() {
return device;
}
public HandshakeStage getHandshakeStage() {
return handshakeStage;
}
public void setHandshakeStage(HandshakeStage handshakeStage) {
this.handshakeStage = handshakeStage;
}
public void setDevice(Device device) {
this.device = device;
}
public void setHeartbeatInterval(int heartbeatInterval) {
this.heartbeatInterval = heartbeatInterval;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
}

View File

@@ -7,40 +7,133 @@ import org.java_websocket.WebSocket;
import com.rosetta.im.protocol.util.StringUtil;
/**
* Клиент, подключенный к серверу.
*/
public class Client {
public WebSocket socket;
public String clientId;
public Map<String, Object> clientData;
/**
* Интервал отправки heartbeat пакетов в миллисекундах.
*/
public long heartbeatInterval = 0;
/**
* Время последнего полученного heartbeat в миллисекундах.
*/
private volatile long lastHeartbeatTime;
public Client(WebSocket socket) {
/**
* Создает нового клиента с указанным сокетом.
* Этот метод используется внутри протокола для управления подключениями клиентов.
* @param socket Веб-сокет клиента.
*
*/
public Client(WebSocket socket, long heartbeatInterval) {
this.socket = socket;
this.clientId = StringUtil.randomString(32);
this.clientData = new HashMap<>();
this.heartbeatInterval = heartbeatInterval;
this.lastHeartbeatTime = System.currentTimeMillis();
}
/**
* Проверяет жив ли клиент на основе времени последнего heartbeat.
* Если с момента последнего heartbeat прошло больше, чем указанный интервал, клиент считается неактивным.
*
* Для того чтобы исключить сетевые задержки, проверка умножает интервал на 2.
* @return
*/
public boolean isAlive() {
return (System.currentTimeMillis() - this.lastHeartbeatTime) * 2 <= this.heartbeatInterval * 1000;
}
/**
* Обновляет время последнего полученного heartbeat на текущее время.
*/
public void updateHeartbeat() {
this.lastHeartbeatTime = System.currentTimeMillis();
}
/**
* Получает уникальный идентификатор клиента.
* @return Идентификатор клиента.
*/
public String getClientId() {
return clientId;
}
/**
* Получает данные, связанные с клиентом.
* @return Данные клиента.
*/
public Map<String, Object> getClientData() {
return clientData;
}
/**
* Устанавливает уникальный идентификатор клиента.
* @param clientId Идентификатор клиента.
*/
public void setClientId(String clientId) {
this.clientId = clientId;
}
/**
* Устанавливает данные для клиента по указанному ключу.
* @param key Ключ данных.
* @param value Значение данных.
*/
public void setData(String key, Object value) {
this.clientData.put(key, value);
}
/**
* Устанавливает данные клиента.
* @param data Данные клиента.
*/
public void setData(Map<String, Object> data) {
this.clientData = data;
}
/**
* Получает данные клиента по указанному ключу.
* @param key Ключ данных.
* @return Значение данных.
*/
public Object getData(String key) {
return this.clientData.get(key);
}
/**
* Получает веб-сокет клиента.
* @return Веб-сокет.
*/
public WebSocket getSocket() {
return socket;
}
/**
* Отключает клиента с указанным кодом.
* @param code Код отключения.
*/
public void disconnect(int code) {
this.socket.close(code);
}
/**
* Отключает клиента с указанным кодом отказа.
* @param code Код отказа.
*/
public void disconnect(Failures code) {
this.disconnect(code.getCode());
}
/**
* Отключает клиента с неизвестной причиной.
*/
public void disconnect() {
this.disconnect(Failures.UNKNOWN_FAILURE);
}
}

View File

@@ -0,0 +1,55 @@
package com.rosetta.im.protocol;
/**
* Перечисление кодов ошибок, используемых в протоколе.
*/
public enum Failures {
/**
* Код ошибки, указывающий на несоответствие данных.
*/
DATA_MISSMATCH(3001),
/**
* Код ошибки, указывающий на незавершенное рукопожатие.
*/
HANDSHAKE_NOT_COMPLETED(3002),
/**
* Код ошибки, указывающий на некорректный пакет.
*/
BAD_PACKET(3003),
/**
* Код ошибки, указывающий на некорректный пакет.
*/
INVALID_PACKET(3003),
/**
* Код ошибки, указывающий на тайм-аут бездействия.
*/
INACTIVITY_TIMEOUT(3004),
/**
* Код ошибки, указывающий на неизвестный тип пакета.
*/
PACKET_ID_FAILURE(3998),
/**
* Код ошибки, указывающий на неизвестный тип пакета.
*/
UNSUPPORTED_PACKET(3998),
/**
* Код ошибки, указывающий на неизвестную ошибку.
*/
UNKNOWN_FAILURE(3999);
private final int code;
Failures(int code) {
this.code = code;
}
/**
* Получает код ошибки.
* @return Код ошибки.
*/
public int getCode() {
return code;
}
}

View File

@@ -2,20 +2,27 @@ package com.rosetta.im.protocol;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import com.rosetta.im.protocol.packet.Packet;
import com.rosetta.im.protocol.packet.PacketExecutor;
import com.rosetta.im.protocol.packet.PacketManager;
public class Server extends WebSocketServer {
private PacketManager packetManager;
private Settings settings;
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public Server(int port, PacketManager packetManager) {
super(new InetSocketAddress(port));
public Server(Settings settings, PacketManager packetManager) {
super(new InetSocketAddress(settings.port));
this.settings = settings;
this.packetManager = packetManager;
}
@@ -30,41 +37,97 @@ public class Server extends WebSocketServer {
}
@Override
public void onMessage(WebSocket arg0, String arg1) {
public void onMessage(WebSocket socket, String message) {
/**
* Обновляем время последнего полученного heartbeat.
* Так как клиент отпраивл нам пакет, он живой.
*/
Client client = socket.getAttachment();
client.updateHeartbeat();
}
@Override
public void onMessage(WebSocket socket, ByteBuffer byteBuffer) {
Client client = socket.getAttachment();
byte[] bytes = byteBuffer.array();
Stream stream = new Stream(bytes);
int packetId = stream.readInt16();
Class<? extends Packet> packetClass = this.packetManager.getPacketClass(packetId);
if(packetClass == null){
System.out.println("Received unknown packet with id: " + packetId);
/**
* Обновляем время последнего полученного heartbeat.
* Так как клиент отпраивл нам пакет, он живой.
*/
client.updateHeartbeat();
if(!this.packetManager.hasPacketSupported(packetId)){
/**
* Если пакет не поддерживается, отключаем клиента с соответствующим кодом ошибки.
*/
client.disconnect(Failures.UNSUPPORTED_PACKET);
return;
}
if(!this.packetManager.hasExecutorDelegated(packetId)){
/**
* Если для пакета не назначен обработчик, отключаем клиента с соответствующим кодом ошибки.
*/
client.disconnect(Failures.UNSUPPORTED_PACKET);
return;
}
Class<? extends Packet> packetClass = this.packetManager.getPacketClass(packetId);
try {
Packet packet = packetClass.getConstructor().newInstance();
packet.packetId = packetId;
/**
* Читаем данные пакета из потока.
*/
packet.read(stream);
System.out.println("Received packet: " + packetClass.getSimpleName());
/**
* Получаем обработчик пакета и вызываем его метод обработки.
*/
Class<? extends PacketExecutor> executorClass = this.packetManager.getExecutors().get(packetId);
PacketExecutor executor = executorClass.getConstructor().newInstance();
executor.onPacketReceived(packet, client);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error while processing packet " + packetClass.getName());
System.out.println(e.getStackTrace());
}
}
@Override
public void onOpen(WebSocket socket, ClientHandshake arg1) {
Client client = new Client(socket);
/**
* Создаем нового клиента при открытии соединения.
* Передаем интервал heartbeat из настроек сервера.
* Если клиент не отправляет heartbeat в указанный интервал, его можно отключить.
*/
Client client = new Client(socket, this.settings.heartbeatInterval);
socket.setAttachment(client);
}
@Override
public void onStart() {
/**
* Настраиваем планировщик для проверки активности клиентов.
*/
this.inactivityShedulerTweaker();
int port = this.getPort();
System.out.println("\u001B[32mServer started at x.x.x.x:" + port + "\u001B[0m");
}
/**
* Планировщик для проверки активности клиентов.
* Если планировщик обнаруживает неактивного клиента, он отключает его с соответствующим кодом ошибки.
*/
public void inactivityShedulerTweaker() {
this.scheduler.scheduleAtFixedRate(() -> {
for(WebSocket socket : this.getConnections()) {
Client client = socket.getAttachment();
if(!client.isAlive()) {
client.disconnect(Failures.INACTIVITY_TIMEOUT);
}
}
}, this.settings.heartbeatInterval, this.settings.heartbeatInterval, TimeUnit.MILLISECONDS);
}
}

View File

@@ -0,0 +1,18 @@
package com.rosetta.im.protocol;
public class Settings {
/**
* Порт сервера
*/
public int port = 8881;
/**
* Интервал отправки heartbeat пакетов в секундах.
* Если клиент не отправляет heartbeat пакеты в течение этого времени, сервер может считать его отключенным.
*/
public long heartbeatInterval = 30;
public Settings(int port, long heartbeatInterval) {
this.port = port;
this.heartbeatInterval = heartbeatInterval;
}
}

View File

@@ -1,5 +1,7 @@
package com.rosetta.im.protocol.packet;
import com.rosetta.im.protocol.Client;
public interface PacketExecutor {
public void onPacketReceived(Class <? extends Packet> packet);
public void onPacketReceived(Packet packet, Client client);
}

View File

@@ -2,20 +2,48 @@ package com.rosetta.im.protocol.packet;
import java.util.HashMap;
/**
* Менеджер сетевых пакетов и их обработчиков.
*/
public class PacketManager {
private HashMap<Integer, Class<? extends Packet>> packets;
private HashMap<Integer, Class<? extends PacketExecutor>> executors;
public PacketManager() {
this.packets = new HashMap<>();
this.executors = new HashMap<>();
}
/**
* Регистрирует пакет с указанным ID.
* @param packetId ID пакета
* @param packet Класс пакета
*/
public void registerPacket(int packetId, Class<? extends Packet> packet) {
this.packets.put(packetId, packet);
}
public boolean isPacketRegistred(Packet packet) {
return this.packets.containsValue(packet.getClass());
/**
* Проверяет, зарегистрирован ли обработчик для пакета с указанным ID.
* @param packetId ID пакета
* @return true, если обработчик зарегистрирован, иначе false.
*/
public boolean hasExecutorDelegated(int packetId) {
return this.executors.containsKey(packetId);
}
/**
* Проверяет, поддерживается ли пакет с указанным ID.
* @param packetId ID пакета
* @return true, если пакет поддерживается, иначе false.
*/
public boolean hasPacketSupported(int packetId) {
return this.packets.containsKey(packetId);
}
public HashMap<Integer, Class<? extends PacketExecutor>> getExecutors() {
return this.executors;
}
public Integer getPacketIdByClass(Class<? extends Packet> packetClass) {
@@ -31,5 +59,33 @@ public class PacketManager {
return this.packets.get(packetId);
}
/**
* Регистрирует обработчик пакета с указанным ID.
* @param packetId ID пакета
* @param executor Обработчик пакета
*/
public void registerExecutor(int packetId, Class <? extends PacketExecutor> executor) {
if (this.executors == null) {
this.executors = new HashMap<>();
}
this.executors.put(packetId, executor);
}
/**
* Возвращает общее количество зарегистрированных обработчиков пакетов.
* @return Количество обработчиков пакетов.
*/
public Integer totalExecutors() {
return this.executors.size();
}
/**
* Возвращает общее количество зарегистрированных пакетов.
* @return Количество пакетов.
*/
public Integer totalPackets() {
return this.packets.size();
}
}