diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..f8f0c7b
--- /dev/null
+++ b/.vscode/settings.json
@@ -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"
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..19b9e3e
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+ com.rosetta.im
+ rosetta-server
+ 1.0-SNAPSHOT
+
+
+ 17
+ 17
+
+
+
+
+
+
+
+
+ org.java-websocket
+ Java-WebSocket
+ 1.6.0
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/rosetta/im/Main.java b/src/main/java/com/rosetta/im/Main.java
new file mode 100644
index 0000000..f09e66a
--- /dev/null
+++ b/src/main/java/com/rosetta/im/Main.java
@@ -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...");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/rosetta/im/device/Device.java b/src/main/java/com/rosetta/im/device/Device.java
new file mode 100644
index 0000000..e3ebfb9
--- /dev/null
+++ b/src/main/java/com/rosetta/im/device/Device.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/com/rosetta/im/executors/Executor0Handshake.java b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java
new file mode 100644
index 0000000..349fab1
--- /dev/null
+++ b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java
@@ -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) {
+
+ }
+
+}
diff --git a/src/main/java/com/rosetta/im/packet/Packet0Handshake.java b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java
new file mode 100644
index 0000000..6389fce
--- /dev/null
+++ b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java
@@ -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()
+ );
+ }
+
+}
diff --git a/src/main/java/com/rosetta/im/packet/enums/HandshakeStage.java b/src/main/java/com/rosetta/im/packet/enums/HandshakeStage.java
new file mode 100644
index 0000000..a581057
--- /dev/null
+++ b/src/main/java/com/rosetta/im/packet/enums/HandshakeStage.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/rosetta/im/protocol/Client.java b/src/main/java/com/rosetta/im/protocol/Client.java
new file mode 100644
index 0000000..58b04a0
--- /dev/null
+++ b/src/main/java/com/rosetta/im/protocol/Client.java
@@ -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 clientData;
+
+ public Client(WebSocket socket) {
+ this.socket = socket;
+ this.clientId = StringUtil.randomString(32);
+ this.clientData = new HashMap<>();
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public Map 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 data) {
+ this.clientData = data;
+ }
+
+ public Object getData(String key) {
+ return this.clientData.get(key);
+ }
+
+}
diff --git a/src/main/java/com/rosetta/im/protocol/Server.java b/src/main/java/com/rosetta/im/protocol/Server.java
new file mode 100644
index 0000000..f1bfaf0
--- /dev/null
+++ b/src/main/java/com/rosetta/im/protocol/Server.java
@@ -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() {
+
+ }
+
+}
diff --git a/src/main/java/com/rosetta/im/protocol/Stream.java b/src/main/java/com/rosetta/im/protocol/Stream.java
new file mode 100644
index 0000000..b54ee64
--- /dev/null
+++ b/src/main/java/com/rosetta/im/protocol/Stream.java
@@ -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;
+ }
+ }
+
+}
diff --git a/src/main/java/com/rosetta/im/protocol/packet/Packet.java b/src/main/java/com/rosetta/im/protocol/packet/Packet.java
new file mode 100644
index 0000000..bb74db9
--- /dev/null
+++ b/src/main/java/com/rosetta/im/protocol/packet/Packet.java
@@ -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);
+
+}
diff --git a/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java b/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java
new file mode 100644
index 0000000..bca1a0a
--- /dev/null
+++ b/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java
@@ -0,0 +1,5 @@
+package com.rosetta.im.protocol.packet;
+
+public interface PacketExecutor {
+ public void onPacketReceived(Class extends Packet> packet);
+}
diff --git a/src/main/java/com/rosetta/im/protocol/packet/PacketManager.java b/src/main/java/com/rosetta/im/protocol/packet/PacketManager.java
new file mode 100644
index 0000000..cabd5dd
--- /dev/null
+++ b/src/main/java/com/rosetta/im/protocol/packet/PacketManager.java
@@ -0,0 +1,35 @@
+package com.rosetta.im.protocol.packet;
+
+import java.util.HashMap;
+
+public class PacketManager {
+
+ private HashMap> 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);
+ }
+
+
+}
diff --git a/src/main/java/com/rosetta/im/protocol/util/StringUtil.java b/src/main/java/com/rosetta/im/protocol/util/StringUtil.java
new file mode 100644
index 0000000..877f270
--- /dev/null
+++ b/src/main/java/com/rosetta/im/protocol/util/StringUtil.java
@@ -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();
+ }
+
+}
diff --git a/target/classes/com/rosetta/im/Main.class b/target/classes/com/rosetta/im/Main.class
new file mode 100644
index 0000000..33955d8
Binary files /dev/null and b/target/classes/com/rosetta/im/Main.class differ
diff --git a/target/classes/com/rosetta/im/device/Device.class b/target/classes/com/rosetta/im/device/Device.class
new file mode 100644
index 0000000..f6959d7
Binary files /dev/null and b/target/classes/com/rosetta/im/device/Device.class differ
diff --git a/target/classes/com/rosetta/im/executors/Executor0Handshake.class b/target/classes/com/rosetta/im/executors/Executor0Handshake.class
new file mode 100644
index 0000000..1b3aead
Binary files /dev/null and b/target/classes/com/rosetta/im/executors/Executor0Handshake.class differ
diff --git a/target/classes/com/rosetta/im/packet/Packet0Handshake.class b/target/classes/com/rosetta/im/packet/Packet0Handshake.class
new file mode 100644
index 0000000..20b0d02
Binary files /dev/null and b/target/classes/com/rosetta/im/packet/Packet0Handshake.class differ
diff --git a/target/classes/com/rosetta/im/packet/enums/HandshakeStage.class b/target/classes/com/rosetta/im/packet/enums/HandshakeStage.class
new file mode 100644
index 0000000..4bec1d7
Binary files /dev/null and b/target/classes/com/rosetta/im/packet/enums/HandshakeStage.class differ
diff --git a/target/classes/com/rosetta/im/protocol/Client.class b/target/classes/com/rosetta/im/protocol/Client.class
new file mode 100644
index 0000000..0a642fb
Binary files /dev/null and b/target/classes/com/rosetta/im/protocol/Client.class differ
diff --git a/target/classes/com/rosetta/im/protocol/Server.class b/target/classes/com/rosetta/im/protocol/Server.class
new file mode 100644
index 0000000..ca113bb
Binary files /dev/null and b/target/classes/com/rosetta/im/protocol/Server.class differ
diff --git a/target/classes/com/rosetta/im/protocol/Stream.class b/target/classes/com/rosetta/im/protocol/Stream.class
new file mode 100644
index 0000000..b023c77
Binary files /dev/null and b/target/classes/com/rosetta/im/protocol/Stream.class differ
diff --git a/target/classes/com/rosetta/im/protocol/packet/Packet.class b/target/classes/com/rosetta/im/protocol/packet/Packet.class
new file mode 100644
index 0000000..d6c377d
Binary files /dev/null and b/target/classes/com/rosetta/im/protocol/packet/Packet.class differ
diff --git a/target/classes/com/rosetta/im/protocol/packet/PacketExecutor.class b/target/classes/com/rosetta/im/protocol/packet/PacketExecutor.class
new file mode 100644
index 0000000..a4c2f31
Binary files /dev/null and b/target/classes/com/rosetta/im/protocol/packet/PacketExecutor.class differ
diff --git a/target/classes/com/rosetta/im/protocol/packet/PacketManager.class b/target/classes/com/rosetta/im/protocol/packet/PacketManager.class
new file mode 100644
index 0000000..972ceaf
Binary files /dev/null and b/target/classes/com/rosetta/im/protocol/packet/PacketManager.class differ
diff --git a/target/classes/com/rosetta/im/protocol/util/StringUtil.class b/target/classes/com/rosetta/im/protocol/util/StringUtil.class
new file mode 100644
index 0000000..14ca2b2
Binary files /dev/null and b/target/classes/com/rosetta/im/protocol/util/StringUtil.class differ