Merge pull request 'master' (#1) from master into main

Reviewed-on: http://172.86.92.132/Rosetta/rosetta-server/pulls/1
This commit is contained in:
RoyceDa
2026-02-01 22:43:44 +00:00
26 changed files with 614 additions and 0 deletions

14
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
".DS_Store": true,
"._*": true,
"**/._*": true
},
"java.project.explorer.showNonJavaResources": false,
"java.configuration.updateBuildConfiguration": "interactive"
}

28
pom.xml Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rosetta.im</groupId>
<artifactId>rosetta-server</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.6.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,20 @@
package com.rosetta.im;
import com.rosetta.im.packet.Packet0Handshake;
import com.rosetta.im.protocol.Server;
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);
Server server = new Server(8881, manager);
server.start();
System.out.println("Rosetta server started...");
}
}

View File

@@ -0,0 +1,39 @@
package com.rosetta.im.device;
public class Device {
public String deviceId;
public String deviceName;
public String deviceOs;
public Device(String deviceId, String deviceName, String deviceOs) {
this.deviceId = deviceId;
this.deviceName = deviceName;
this.deviceOs = deviceOs;
}
public String getDeviceId() {
return deviceId;
}
public String getDeviceName() {
return deviceName;
}
public String getDeviceOs() {
return deviceOs;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setDeviceOs(String deviceOs) {
this.deviceOs = deviceOs;
}
}

View File

@@ -0,0 +1,13 @@
package com.rosetta.im.executors;
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) {
}
}

View File

@@ -0,0 +1,83 @@
package com.rosetta.im.packet;
import com.rosetta.im.device.Device;
import com.rosetta.im.packet.enums.HandshakeStage;
import com.rosetta.im.protocol.Stream;
import com.rosetta.im.protocol.packet.Packet;
/**
* Пакет хэндшейка между клиентом и сервером.
* Используется для установления соединения и подтверждения от сервера что клиент
* тот за кого себя выдает.
*
* Протокол таблица:
* 0 - packetId (int16)
* 1 - privateKey (string)
* 2 - publicKey (string)
* 3 - protocolVersion (int8)
* 4 - heartbeatInterval (int8)
* 5 - deviceId (string)
* 6 - deviceName (string)
* 7 - deviceOs (string)
* 8 - handshakeStage (int8)
*/
public class Packet0Handshake extends Packet {
/**
* Публичный и приватный ключи клиента
*/
private String publicKey;
private String privateKey;
/**
* Версия протокола клиента
*/
private int protocolVersion = 1;
/**
* Интервал отправки heartbeat пакетов в секундах
*/
private int heartbeatInterval = 15;
/**
* Минимальная информация об устройстве клиента
*/
private Device device;
/**
* Стадия рукопожатия
* 0 - COMPLETED
* 1 - NEED_DEVICE_VERIFICATION
*/
private HandshakeStage handshakeStage = HandshakeStage.COMPLETED;
@Override
public Stream write() {
Stream stream = new Stream();
stream.writeInt16(this.packetId);
stream.writeString(this.privateKey);
stream.writeString(this.publicKey);
stream.writeInt8(this.protocolVersion);
stream.writeInt8(this.heartbeatInterval);
stream.writeString(this.device.getDeviceId());
stream.writeString(this.device.getDeviceName());
stream.writeString(this.device.getDeviceOs());
stream.writeInt8(this.handshakeStage.getCode());
return stream;
}
@Override
public void read(Stream stream) {
this.privateKey = stream.readString();
this.publicKey = stream.readString();
this.protocolVersion = stream.readInt8();
this.heartbeatInterval = stream.readInt8();
String deviceId = stream.readString();
String deviceName = stream.readString();
String deviceOs = stream.readString();
this.device = new Device(deviceId, deviceName, deviceOs);
this.handshakeStage = HandshakeStage.fromCode(
stream.readInt8()
);
}
}

View File

@@ -0,0 +1,36 @@
package com.rosetta.im.packet.enums;
/**
* Этапы хэндшейка между клиентом и сервером.
*/
public enum HandshakeStage {
/**
* Успешный хэндшейк
* Такой пользователь может авторизованно взаимодействовать с сервером
*/
COMPLETED(0),
/**
* Необходима верификация устройства
* Такой пользователь должен подтвердить устройство (например, через код на другом устройстве)
*/
NEED_DEVICE_VERIFICATION(1);
private final int code;
HandshakeStage(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public static HandshakeStage fromCode(int code) {
for (HandshakeStage stage : HandshakeStage.values()) {
if (stage.getCode() == code) {
return stage;
}
}
throw new IllegalArgumentException("Invalid HandshakeStage code: " + code);
}
}

View File

@@ -0,0 +1,46 @@
package com.rosetta.im.protocol;
import java.util.HashMap;
import java.util.Map;
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;
public Client(WebSocket socket) {
this.socket = socket;
this.clientId = StringUtil.randomString(32);
this.clientData = new HashMap<>();
}
public String getClientId() {
return clientId;
}
public Map<String, Object> getClientData() {
return clientData;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public void setData(String key, Object value) {
this.clientData.put(key, value);
}
public void setData(Map<String, Object> data) {
this.clientData = data;
}
public Object getData(String key) {
return this.clientData.get(key);
}
}

View File

@@ -0,0 +1,70 @@
package com.rosetta.im.protocol;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
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.PacketManager;
public class Server extends WebSocketServer {
private PacketManager packetManager;
public Server(int port, PacketManager packetManager) {
super(new InetSocketAddress(port));
this.packetManager = packetManager;
}
@Override
public void onClose(WebSocket arg0, int arg1, String arg2, boolean arg3) {
}
@Override
public void onError(WebSocket arg0, Exception arg1) {
}
@Override
public void onMessage(WebSocket arg0, String arg1) {
}
@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);
return;
}
try {
Packet packet = packetClass.getConstructor().newInstance();
packet.packetId = packetId;
packet.read(stream);
System.out.println("Received packet: " + packetClass.getSimpleName());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onOpen(WebSocket socket, ClientHandshake arg1) {
Client client = new Client(socket);
socket.setAttachment(client);
}
@Override
public void onStart() {
}
}

View File

@@ -0,0 +1,179 @@
package com.rosetta.im.protocol;
public class Stream {
private byte[] stream;
private int readPointer = 0;
private int writePointer = 0;
public Stream() {
this.stream = new byte[0];
}
public Stream(byte[] stream) {
this.stream = stream;
}
public byte[] getStream() {
return this.stream;
}
public void setStream(byte[] stream) {
this.stream = stream;
}
public void writeInt8(int value) {
int negationBit = value < 0 ? 1 : 0;
int int8Value = Math.abs(value) & 0xFF;
ensureCapacity(writePointer >> 3);
stream[writePointer >> 3] |= (byte)(negationBit << (7 - (writePointer & 7)));
writePointer++;
for (int i = 0; i < 8; i++) {
int bit = (int8Value >> (7 - i)) & 1;
ensureCapacity(writePointer >> 3);
stream[writePointer >> 3] |= (byte)(bit << (7 - (writePointer & 7)));
writePointer++;
}
}
public int readInt8() {
int value = 0;
int negationBit = (stream[readPointer >> 3] >> (7 - (readPointer & 7))) & 1;
readPointer++;
for (int i = 0; i < 8; i++) {
int bit = (stream[readPointer >> 3] >> (7 - (readPointer & 7))) & 1;
value |= bit << (7 - i);
readPointer++;
}
return negationBit == 1 ? -value : value;
}
public void writeBit(int value) {
int bit = value & 1;
ensureCapacity(writePointer >> 3);
stream[writePointer >> 3] |= (byte)(bit << (7 - (writePointer & 7)));
writePointer++;
}
public int readBit() {
int bit = (stream[readPointer >> 3] >> (7 - (readPointer & 7))) & 1;
readPointer++;
return bit;
}
public void writeBoolean(boolean value) {
writeBit(value ? 1 : 0);
}
public boolean readBoolean() {
return readBit() == 1;
}
public void writeInt16(int value) {
writeInt8(value >> 8);
writeInt8(value & 0xFF);
}
public int readInt16() {
int value = readInt8() << 8;
return value | readInt8();
}
public void writeInt32(int value) {
writeInt16(value >> 16);
writeInt16(value & 0xFFFF);
}
public int readInt32() {
int value = readInt16() << 16;
return value | readInt16();
}
public void writeInt64(long value) {
int high = (int)(value >> 32);
int low = (int)value;
writeInt32(high);
writeInt32(low);
}
public long readInt64() {
long high = readInt32();
long low = readInt32() & 0xFFFFFFFFL;
return (high << 32) | low;
}
public void writeFloat32(float value) {
int floatValue = Float.floatToIntBits(value);
writeInt32(floatValue);
}
public float readFloat32() {
int floatValue = readInt32();
return Float.intBitsToFloat(floatValue);
}
public void writeString(String value) {
int length = value.length();
writeInt32(length);
for (int i = 0; i < value.length(); i++) {
writeInt16(value.charAt(i));
}
}
public String readString() {
int length = readInt32();
// Security fix for string length exceeding stream capacity
if (length < 0 || length > (stream.length - (readPointer >> 3))) {
System.out.println("Stream readString length invalid: " + length + ", stream length: " + stream.length + ", readPointer: " + readPointer);
return "";
}
StringBuilder value = new StringBuilder();
for (int i = 0; i < length; i++) {
value.append((char)readInt16());
}
return value.toString();
}
public void writeBytes(byte[] value) {
writeInt32(value.length);
for (int i = 0; i < value.length; i++) {
writeInt8(value[i]);
}
}
public byte[] readBytes() {
int length = readInt32();
byte[] value = new byte[length];
for (int i = 0; i < length; i++) {
value[i] = (byte)readInt8();
}
return value;
}
public boolean isEmpty() {
return this.stream.length == 0;
}
public int length() {
return this.stream.length;
}
public byte[] getBuffer() {
return this.stream;
}
private void ensureCapacity(int byteIndex) {
if (byteIndex >= stream.length) {
byte[] newStream = new byte[byteIndex + 1];
System.arraycopy(stream, 0, newStream, 0, stream.length);
stream = newStream;
}
}
}

View File

@@ -0,0 +1,31 @@
package com.rosetta.im.protocol.packet;
import com.rosetta.im.protocol.Stream;
/**
* Представляет собой абстрактный класс для всех пакетов, используемых в протоколе.
*/
public abstract class Packet {
public int packetId;
public PacketManager packetManager;
public Packet() {
}
/**
* Записывает данные пакета в поток. Исползуется при отправке
*
* @return Поток с записанными данными пакета.
*/
public abstract Stream write();
/**
* Читает данные пакета из потока. Используется при получении
*
* @param stream Поток с данными пакета.
*/
public abstract void read(Stream stream);
}

View File

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

View File

@@ -0,0 +1,35 @@
package com.rosetta.im.protocol.packet;
import java.util.HashMap;
public class PacketManager {
private HashMap<Integer, Class<? extends Packet>> packets;
public PacketManager() {
this.packets = new HashMap<>();
}
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());
}
public Integer getPacketIdByClass(Class<? extends Packet> packetClass) {
for (var entry : this.packets.entrySet()) {
if (entry.getValue().equals(packetClass)) {
return entry.getKey();
}
}
return null;
}
public Class<? extends Packet> getPacketClass(int packetId) {
return this.packets.get(packetId);
}
}

View File

@@ -0,0 +1,15 @@
package com.rosetta.im.protocol.util;
public class StringUtil {
public static String randomString(int length) {
StringBuilder sb = new StringBuilder();
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (int i = 0; i < length; i++) {
int index = (int) (Math.random() * characters.length());
sb.append(characters.charAt(index));
}
return sb.toString();
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.