diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 21b1c22..d40619c 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,27 +2,15 @@ version: '3.8' services: db: - image: postgres:16-alpine + image: postgres:latest environment: - POSTGRES_DB: rosetta_db - POSTGRES_USER: rosetta_user - POSTGRES_PASSWORD: rosetta_password + POSTGRES_DB: your_database + POSTGRES_USER: your_user + POSTGRES_PASSWORD: your_password ports: - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U rosetta_user"] - interval: 10s - timeout: 5s - retries: 5 adminer: image: adminer ports: - - "8080:8080" - depends_on: - - db - -volumes: - postgres_data: \ No newline at end of file + - "8080:8080" \ No newline at end of file diff --git a/src/main/java/com/rosetta/im/Configuration.java b/src/main/java/com/rosetta/im/Configuration.java new file mode 100644 index 0000000..be0b559 --- /dev/null +++ b/src/main/java/com/rosetta/im/Configuration.java @@ -0,0 +1,7 @@ +package com.rosetta.im; + +public class Configuration { + + public static final int PROTOCOL_VERSION = 1; + +} diff --git a/src/main/java/com/rosetta/im/Failures.java b/src/main/java/com/rosetta/im/Failures.java new file mode 100644 index 0000000..7309553 --- /dev/null +++ b/src/main/java/com/rosetta/im/Failures.java @@ -0,0 +1,26 @@ +package com.rosetta.im; + +import com.rosetta.im.protocol.BaseFailures; + +public enum Failures implements BaseFailures { + /** + * Код ошибки, указывающий на неизвестную ошибку. + */ + UNSUPPORTED_PROTOCOL(3008); + + private final int code; + + Failures(int code) { + this.code = code; + } + + /** + * Получает код ошибки. + * @return Код ошибки. + */ + public int getCode() { + return code; + } + + +} diff --git a/src/main/java/com/rosetta/im/Main.java b/src/main/java/com/rosetta/im/Main.java index 20f20de..492f78e 100644 --- a/src/main/java/com/rosetta/im/Main.java +++ b/src/main/java/com/rosetta/im/Main.java @@ -22,7 +22,7 @@ public class Main { /** * Запуск сервера на порту 8881 */ - Server server = new Server(settings, manager); + Server server = new Server(settings, manager, Configuration.class); server.start(); } } \ No newline at end of file diff --git a/src/main/java/com/rosetta/im/client/tags/Authentificate.java b/src/main/java/com/rosetta/im/client/tags/Authentificate.java new file mode 100644 index 0000000..82c350b --- /dev/null +++ b/src/main/java/com/rosetta/im/client/tags/Authentificate.java @@ -0,0 +1,68 @@ +package com.rosetta.im.client.tags; + +import com.rosetta.im.packet.enums.HandshakeStage; +import com.rosetta.im.protocol.ECITag; + +/** + * Это вложенный обьект для клиента, содержащий информацию об аутентификации. + */ +public class Authentificate extends ECITag { + + public Device device; + public String publicKey; + public String privateKey; + public HandshakeStage handshakeStage; + + public Authentificate() { + super("authentificate"); + } + + public Authentificate(Device device, String publicKey, String privateKey, HandshakeStage handshakeStage) { + super("authentificate"); + this.device = device; + this.publicKey = publicKey; + this.privateKey = privateKey; + this.handshakeStage = handshakeStage; + } + + public Device getDevice() { + return device; + } + + public String getPublicKey() { + return publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public HandshakeStage getHandshakeStage() { + return handshakeStage; + } + + public void setDevice(Device device) { + this.device = device; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + public void setHandshakeStage(HandshakeStage handshakeStage) { + this.handshakeStage = handshakeStage; + } + + /** + * Проверяет, прошел ли клиент аутентификацию. В том числе подтвердил ли устройство. + * @return true если аутентификация пройдена, иначе false. + */ + public boolean hasAuthorized() { + return this.handshakeStage == HandshakeStage.COMPLETED; + } + +} diff --git a/src/main/java/com/rosetta/im/device/Device.java b/src/main/java/com/rosetta/im/client/tags/Device.java similarity index 95% rename from src/main/java/com/rosetta/im/device/Device.java rename to src/main/java/com/rosetta/im/client/tags/Device.java index e3ebfb9..e323afe 100644 --- a/src/main/java/com/rosetta/im/device/Device.java +++ b/src/main/java/com/rosetta/im/client/tags/Device.java @@ -1,4 +1,4 @@ -package com.rosetta.im.device; +package com.rosetta.im.client.tags; public class Device { diff --git a/src/main/java/com/rosetta/im/database/DatabaseManager.java b/src/main/java/com/rosetta/im/database/DatabaseManager.java index 62029b7..bbb5c1c 100644 --- a/src/main/java/com/rosetta/im/database/DatabaseManager.java +++ b/src/main/java/com/rosetta/im/database/DatabaseManager.java @@ -3,6 +3,7 @@ package com.rosetta.im.database; import org.hibernate.Session; import org.hibernate.Transaction; +import java.util.HashMap; import java.util.List; public class DatabaseManager { @@ -61,6 +62,28 @@ public class DatabaseManager { } } + public static T findBy(Class entity, HashMap params) { + Session session = HibernateUtil.openSession(); + try { + StringBuilder queryString = new StringBuilder("FROM " + entity.getSimpleName() + " WHERE "); + int index = 0; + for (String key : params.keySet()) { + if (index > 0) { + queryString.append(" AND "); + } + queryString.append(key).append(" = :").append(key); + index++; + } + var query = session.createQuery(queryString.toString(), entity); + for (var entry : params.entrySet()) { + query.setParameter(entry.getKey(), entry.getValue()); + } + return query.uniqueResult(); + } finally { + session.close(); + } + } + public static void delete(T entity) { Session session = HibernateUtil.openSession(); Transaction transaction = null; diff --git a/src/main/java/com/rosetta/im/database/entity/User.java b/src/main/java/com/rosetta/im/database/entity/User.java index e6d7a40..d757604 100644 --- a/src/main/java/com/rosetta/im/database/entity/User.java +++ b/src/main/java/com/rosetta/im/database/entity/User.java @@ -43,9 +43,6 @@ public class User extends BaseEntity { @Column(name = "publicKey", nullable = false, unique = true) private String publicKey; - @Column(name = "timestamp", nullable = false) - private long timestamp; - @Column(name = "createdTime", nullable = false, updatable = false) private Date createdTime; @@ -104,14 +101,6 @@ public class User extends BaseEntity { this.publicKey = publicKey; } - public long getTimestamp() { - return timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - public Date getCreatedTime() { return createdTime; } diff --git a/src/main/java/com/rosetta/im/executors/Executor0Handshake.java b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java index eda8ba5..7f99260 100644 --- a/src/main/java/com/rosetta/im/executors/Executor0Handshake.java +++ b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java @@ -1,18 +1,90 @@ package com.rosetta.im.executors; +import java.util.HashMap; + +import com.rosetta.im.Configuration; +import com.rosetta.im.Failures; +import com.rosetta.im.client.tags.Authentificate; +import com.rosetta.im.client.tags.Device; +import com.rosetta.im.database.DatabaseManager; +import com.rosetta.im.database.entity.User; import com.rosetta.im.packet.Packet0Handshake; +import com.rosetta.im.packet.enums.HandshakeStage; 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 { +public class Executor0Handshake extends PacketExecutor { @Override public void onPacketReceived(Packet packet, Client client) { Packet0Handshake handshake = (Packet0Handshake) packet; String publicKey = handshake.getPublicKey(); String privateKey = handshake.getPrivateKey(); - int b = 0; + Device device = handshake.getDevice(); + int protocolVersion = handshake.getProtocolVersion(); + + /** + * Получаем информацию об аутентификации клиента + * используя возможности ECI тэгов. + */ + Authentificate authentificate = client.getTag(new Authentificate()); + if(authentificate != null && authentificate.hasAuthorized()) { + /** + * Клиент уже авторизован, повторный хэндшейк не допускается + */ + return; + } + + /** + * Проверяем корректность версии протокола + */ + if(protocolVersion != Configuration.PROTOCOL_VERSION) { + client.disconnect(Failures.UNSUPPORTED_PROTOCOL); + return; + } + + /** + * Проверяем есть ли такой пользователь + */ + User user = DatabaseManager.findBy(User.class, new HashMap<>() {{ + put("publicKey", publicKey); + }}); + + if(user == null) { + /** + * Пользователь не найден, создаем нового + */ + user = new User(); + user.setPrivateKey(privateKey); + user.setPublicKey(publicKey); + user.setUsername(""); + user.setTitle(publicKey.substring(0, 7)); + /** + * Новый пользователь не верифицирован + */ + user.setVerified(0); + + DatabaseManager.save(user); + + Authentificate eciTag = new Authentificate(device, publicKey, privateKey, HandshakeStage.COMPLETED); + client.addTag(eciTag); + + + System.out.println("New user created: " + user.getId()); + + Authentificate authTag = client.getTag(new Authentificate()); + + System.out.println("eciTag" + authTag); + + + return; + } + + + + + } } diff --git a/src/main/java/com/rosetta/im/packet/Packet0Handshake.java b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java index f21ec45..11480a1 100644 --- a/src/main/java/com/rosetta/im/packet/Packet0Handshake.java +++ b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java @@ -1,6 +1,6 @@ package com.rosetta.im.packet; -import com.rosetta.im.device.Device; +import com.rosetta.im.client.tags.Device; import com.rosetta.im.packet.enums.HandshakeStage; import com.rosetta.im.protocol.Stream; import com.rosetta.im.protocol.packet.Packet; diff --git a/src/main/java/com/rosetta/im/protocol/BaseFailures.java b/src/main/java/com/rosetta/im/protocol/BaseFailures.java new file mode 100644 index 0000000..91c2c03 --- /dev/null +++ b/src/main/java/com/rosetta/im/protocol/BaseFailures.java @@ -0,0 +1,9 @@ +package com.rosetta.im.protocol; + +public interface BaseFailures { + /** + * Получает код ошибки. + * @return + */ + int getCode(); +} diff --git a/src/main/java/com/rosetta/im/protocol/Client.java b/src/main/java/com/rosetta/im/protocol/Client.java index a285afe..0656f76 100644 --- a/src/main/java/com/rosetta/im/protocol/Client.java +++ b/src/main/java/com/rosetta/im/protocol/Client.java @@ -1,7 +1,7 @@ package com.rosetta.im.protocol; -import java.util.HashMap; -import java.util.Map; +import java.util.HashSet; +import java.util.Set; import org.java_websocket.WebSocket; @@ -14,7 +14,10 @@ public class Client { public WebSocket socket; public String clientId; - public Map clientData; + /** + * Любые данные, связанные с клиентом. + */ + public Set eciTags; /** * Интервал отправки heartbeat пакетов в миллисекундах. */ @@ -33,7 +36,7 @@ public class Client { public Client(WebSocket socket, long heartbeatInterval) { this.socket = socket; this.clientId = StringUtil.randomString(32); - this.clientData = new HashMap<>(); + this.eciTags = new HashSet(); this.heartbeatInterval = heartbeatInterval; this.lastHeartbeatTime = System.currentTimeMillis(); } @@ -68,8 +71,8 @@ public class Client { * Получает данные, связанные с клиентом. * @return Данные клиента. */ - public Map getClientData() { - return clientData; + public Set getEciTags() { + return this.eciTags; } /** @@ -85,16 +88,16 @@ public class Client { * @param key Ключ данных. * @param value Значение данных. */ - public void setData(String key, Object value) { - this.clientData.put(key, value); + public void addTag(T eciTag) { + this.eciTags.add(eciTag); } /** * Устанавливает данные клиента. * @param data Данные клиента. */ - public void setData(Map data) { - this.clientData = data; + public void setTags(Set eciTags) { + this.eciTags = eciTags; } /** @@ -102,8 +105,27 @@ public class Client { * @param key Ключ данных. * @return Значение данных. */ - public Object getData(String key) { - return this.clientData.get(key); + public T getTag(ECITag eciTag) { + for (ECITag tag : this.eciTags) { + if (tag.getKey().equals(eciTag.getKey())) { + return (T) tag; + } + } + return null; + } + + /** + * Проверяет наличие данных клиента по указанному ключу. + * @param key Ключ данных. + * @return true если данные существуют, иначе false. + */ + public boolean hasTag(ECITag eciTag) { + for (ECITag tag : this.eciTags) { + if (tag.getKey().equals(eciTag.getKey())) { + return true; + } + } + return false; } /** @@ -125,7 +147,7 @@ public class Client { * Отключает клиента с указанным кодом отказа. * @param code Код отказа. */ - public void disconnect(Failures code) { + public void disconnect(BaseFailures code) { this.disconnect(code.getCode()); } @@ -133,7 +155,7 @@ public class Client { * Отключает клиента с неизвестной причиной. */ public void disconnect() { - this.disconnect(Failures.UNKNOWN_FAILURE); + this.disconnect(ServerFailures.UNKNOWN_FAILURE); } } diff --git a/src/main/java/com/rosetta/im/protocol/ECITag.java b/src/main/java/com/rosetta/im/protocol/ECITag.java new file mode 100644 index 0000000..efc4e2d --- /dev/null +++ b/src/main/java/com/rosetta/im/protocol/ECITag.java @@ -0,0 +1,31 @@ +package com.rosetta.im.protocol; + +/** + * Embedded Client Information Tag. + * + * Используется для хранения дополнительной информации о клиенте. + */ +public abstract class ECITag { + + /** + * Ключ тега. + */ + public String key; + + /** + * Создает новый тег с указанным ключом и значением. + * @param key Ключ тега. + */ + public ECITag(String key) { + this.key = key; + } + + /** + * Получает ключ тега. + * @return Ключ тега. + */ + public String getKey() { + return this.key; + } + +} diff --git a/src/main/java/com/rosetta/im/protocol/Server.java b/src/main/java/com/rosetta/im/protocol/Server.java index 3f99a24..effe878 100644 --- a/src/main/java/com/rosetta/im/protocol/Server.java +++ b/src/main/java/com/rosetta/im/protocol/Server.java @@ -19,11 +19,13 @@ public class Server extends WebSocketServer { private PacketManager packetManager; private Settings settings; private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private Object attachment; - public Server(Settings settings, PacketManager packetManager) { + public Server(Settings settings, PacketManager packetManager, Object attachment) { super(new InetSocketAddress(settings.port)); this.settings = settings; this.packetManager = packetManager; + this.attachment = attachment; } @Override @@ -62,14 +64,14 @@ public class Server extends WebSocketServer { /** * Если пакет не поддерживается, отключаем клиента с соответствующим кодом ошибки. */ - client.disconnect(Failures.UNSUPPORTED_PACKET); + client.disconnect(ServerFailures.UNSUPPORTED_PACKET); return; } if(!this.packetManager.hasExecutorDelegated(packetId)){ /** * Если для пакета не назначен обработчик, отключаем клиента с соответствующим кодом ошибки. */ - client.disconnect(Failures.UNSUPPORTED_PACKET); + client.disconnect(ServerFailures.UNSUPPORTED_PACKET); return; } Class packetClass = this.packetManager.getPacketClass(packetId); @@ -86,6 +88,8 @@ public class Server extends WebSocketServer { */ Class executorClass = this.packetManager.getExecutors().get(packetId); PacketExecutor executor = executorClass.getConstructor().newInstance(); + executor.settings = this.settings; + executor.attachment = this.attachment; executor.onPacketReceived(packet, client); } catch (Exception e) { System.out.println("Error while processing packet " + packetClass.getName()); @@ -124,7 +128,7 @@ public class Server extends WebSocketServer { for(WebSocket socket : this.getConnections()) { Client client = socket.getAttachment(); if(!client.isAlive()) { - client.disconnect(Failures.INACTIVITY_TIMEOUT); + client.disconnect(ServerFailures.INACTIVITY_TIMEOUT); } } }, this.settings.heartbeatInterval, this.settings.heartbeatInterval, TimeUnit.MILLISECONDS); diff --git a/src/main/java/com/rosetta/im/protocol/Failures.java b/src/main/java/com/rosetta/im/protocol/ServerFailures.java similarity index 94% rename from src/main/java/com/rosetta/im/protocol/Failures.java rename to src/main/java/com/rosetta/im/protocol/ServerFailures.java index ac36bbd..ddf32ea 100644 --- a/src/main/java/com/rosetta/im/protocol/Failures.java +++ b/src/main/java/com/rosetta/im/protocol/ServerFailures.java @@ -3,7 +3,7 @@ package com.rosetta.im.protocol; /** * Перечисление кодов ошибок, используемых в протоколе. */ -public enum Failures { +public enum ServerFailures implements BaseFailures { /** * Код ошибки, указывающий на несоответствие данных. */ @@ -40,7 +40,7 @@ public enum Failures { private final int code; - Failures(int code) { + ServerFailures(int code) { this.code = code; } diff --git a/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java b/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java index c1bac51..cd197be 100644 --- a/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java +++ b/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java @@ -1,7 +1,35 @@ package com.rosetta.im.protocol.packet; import com.rosetta.im.protocol.Client; +import com.rosetta.im.protocol.Settings; -public interface PacketExecutor { - public void onPacketReceived(Packet packet, Client client); +/** + * Базовый класс для обработчиков пакетов. + */ +public abstract class PacketExecutor { + public Settings settings; + public Object attachment; + + /** + * Настройки сервера. + * @return + */ + public Settings getSettings() { + return settings; + } + + /** + * Вложенный обьект, который был передан при создании сервера. + * @return вложенный обьект + */ + public Object getAttachment() { + return attachment; + } + + /** + * Вызывается при получении пакета от клиента. + * @param packet Пакет, полученный от клиента. + * @param client Клиент, отправивший пакет. + */ + public abstract void onPacketReceived(Packet packet, Client client); } diff --git a/src/main/resources/hibernate.cfg.xml b/src/main/resources/hibernate.cfg.xml index 80bb79d..dcf79da 100644 --- a/src/main/resources/hibernate.cfg.xml +++ b/src/main/resources/hibernate.cfg.xml @@ -3,10 +3,10 @@ org.postgresql.Driver - jdbc:postgresql://localhost:5432/rosetta_db - rosetta_user - rosetta_password - org.hibernate.dialect.PostgreSQLDialect + jdbc:postgresql://localhost:5432/your_database + your_user + your_password + update true true diff --git a/target/classes/com/rosetta/im/Main.class b/target/classes/com/rosetta/im/Main.class index 4dd96c5..c5f68bb 100644 Binary files a/target/classes/com/rosetta/im/Main.class 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 deleted file mode 100644 index f6959d7..0000000 Binary files a/target/classes/com/rosetta/im/device/Device.class and /dev/null differ diff --git a/target/classes/com/rosetta/im/executors/Executor0Handshake.class b/target/classes/com/rosetta/im/executors/Executor0Handshake.class index bd5c676..fddac12 100644 Binary files a/target/classes/com/rosetta/im/executors/Executor0Handshake.class 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 index 3210067..3b91468 100644 Binary files a/target/classes/com/rosetta/im/packet/Packet0Handshake.class and b/target/classes/com/rosetta/im/packet/Packet0Handshake.class differ diff --git a/target/classes/com/rosetta/im/protocol/Client.class b/target/classes/com/rosetta/im/protocol/Client.class index f718965..42efb97 100644 Binary files a/target/classes/com/rosetta/im/protocol/Client.class 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 index c49548f..cbcc02d 100644 Binary files a/target/classes/com/rosetta/im/protocol/Server.class and b/target/classes/com/rosetta/im/protocol/Server.class differ diff --git a/target/classes/com/rosetta/im/protocol/packet/PacketExecutor.class b/target/classes/com/rosetta/im/protocol/packet/PacketExecutor.class index 7706236..047954b 100644 Binary files a/target/classes/com/rosetta/im/protocol/packet/PacketExecutor.class and b/target/classes/com/rosetta/im/protocol/packet/PacketExecutor.class differ