diff --git a/src/main/java/com/rosetta/im/Failures.java b/src/main/java/com/rosetta/im/Failures.java index 38cdbc4..4b422f0 100644 --- a/src/main/java/com/rosetta/im/Failures.java +++ b/src/main/java/com/rosetta/im/Failures.java @@ -4,7 +4,12 @@ import io.orprotocol.BaseFailures; public enum Failures implements BaseFailures { /** - * Код ошибки, указывающий на неизвестную ошибку. + * Ошибка аутентификации + */ + AUTHENTIFICATION_ERROR(3001), + DATA_MISSMATCH(3001), + /** + * Неподдерживаемый протокол */ UNSUPPORTED_PROTOCOL(3008); diff --git a/src/main/java/com/rosetta/im/client/tags/Authentificate.java b/src/main/java/com/rosetta/im/client/tags/ECIAuthentificate.java similarity index 75% rename from src/main/java/com/rosetta/im/client/tags/Authentificate.java rename to src/main/java/com/rosetta/im/client/tags/ECIAuthentificate.java index 932eda6..01f0116 100644 --- a/src/main/java/com/rosetta/im/client/tags/Authentificate.java +++ b/src/main/java/com/rosetta/im/client/tags/ECIAuthentificate.java @@ -7,29 +7,18 @@ import io.orprotocol.client.ECITag; /** * Это вложенный обьект для клиента, содержащий информацию об аутентификации. */ -public class Authentificate extends ECITag { +public class ECIAuthentificate 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; + public ECIAuthentificate(String publicKey, String privateKey, HandshakeStage handshakeStage) { this.publicKey = publicKey; this.privateKey = privateKey; this.handshakeStage = handshakeStage; } - public Device getDevice() { - return device; - } - public String getPublicKey() { return publicKey; } @@ -42,10 +31,6 @@ public class Authentificate extends ECITag { return handshakeStage; } - public void setDevice(Device device) { - this.device = device; - } - public void setPublicKey(String publicKey) { this.publicKey = publicKey; } diff --git a/src/main/java/com/rosetta/im/client/tags/Device.java b/src/main/java/com/rosetta/im/client/tags/ECIDevice.java similarity index 82% rename from src/main/java/com/rosetta/im/client/tags/Device.java rename to src/main/java/com/rosetta/im/client/tags/ECIDevice.java index e323afe..f24918e 100644 --- a/src/main/java/com/rosetta/im/client/tags/Device.java +++ b/src/main/java/com/rosetta/im/client/tags/ECIDevice.java @@ -1,12 +1,14 @@ package com.rosetta.im.client.tags; -public class Device { +import io.orprotocol.client.ECITag; + +public class ECIDevice extends ECITag { public String deviceId; public String deviceName; public String deviceOs; - public Device(String deviceId, String deviceName, String deviceOs) { + public ECIDevice(String deviceId, String deviceName, String deviceOs) { this.deviceId = deviceId; this.deviceName = deviceName; this.deviceOs = deviceOs; diff --git a/src/main/java/com/rosetta/im/database/BaseEntity.java b/src/main/java/com/rosetta/im/database/BaseEntity.java deleted file mode 100644 index bb11391..0000000 --- a/src/main/java/com/rosetta/im/database/BaseEntity.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.rosetta.im.database; - -public class BaseEntity { - - /** - * Сохраняет сущность в базе данных. - * @param entity Сущность для сохранения. - */ - public void save(Object entity) { - DatabaseManager.save(entity); - } - - /** - * Обновляет сущность в базе данных. - * @param entity Сущность для обновления. - */ - public void update(Object entity) { - DatabaseManager.update(entity); - } - - /** - * Удаляет сущность из базы данных. - * @param entity Сущность для удаления. - */ - public void delete(Object entity) { - DatabaseManager.delete(entity); - } - -} diff --git a/src/main/java/com/rosetta/im/database/CreateUpdateEntity.java b/src/main/java/com/rosetta/im/database/CreateUpdateEntity.java new file mode 100644 index 0000000..77fe545 --- /dev/null +++ b/src/main/java/com/rosetta/im/database/CreateUpdateEntity.java @@ -0,0 +1,42 @@ +package com.rosetta.im.database; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; + +/** + * Базовый класс для сущностей с полями + * времени создания и обновления + */ +@MappedSuperclass +public class CreateUpdateEntity { + + @Column(name = "createdAt", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updatedAt", nullable = false) + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + +} diff --git a/src/main/java/com/rosetta/im/database/DatabaseManager.java b/src/main/java/com/rosetta/im/database/DatabaseManager.java deleted file mode 100644 index bbb5c1c..0000000 --- a/src/main/java/com/rosetta/im/database/DatabaseManager.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.rosetta.im.database; - -import org.hibernate.Session; -import org.hibernate.Transaction; - -import java.util.HashMap; -import java.util.List; - -public class DatabaseManager { - - public static T save(T entity) { - Session session = HibernateUtil.openSession(); - Transaction transaction = null; - try { - transaction = session.beginTransaction(); - session.persist(entity); - transaction.commit(); - return entity; - } catch (Exception e) { - if (transaction != null) { - transaction.rollback(); - } - throw new RuntimeException("Error saving entity: " + e.getMessage(), e); - } finally { - session.close(); - } - } - - public static T update(T entity) { - Session session = HibernateUtil.openSession(); - Transaction transaction = null; - try { - transaction = session.beginTransaction(); - session.merge(entity); - transaction.commit(); - return entity; - } catch (Exception e) { - if (transaction != null) { - transaction.rollback(); - } - throw new RuntimeException("Error updating entity: " + e.getMessage(), e); - } finally { - session.close(); - } - } - - public static T findById(Class entityClass, Long id) { - Session session = HibernateUtil.openSession(); - try { - return session.find(entityClass, id); - } finally { - session.close(); - } - } - - public static List findAll(Class entityClass) { - Session session = HibernateUtil.openSession(); - try { - return session.createQuery("FROM " + entityClass.getSimpleName(), entityClass).list(); - } finally { - session.close(); - } - } - - 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; - try { - transaction = session.beginTransaction(); - session.remove(entity); - transaction.commit(); - } catch (Exception e) { - if (transaction != null) { - transaction.rollback(); - } - throw new RuntimeException("Error deleting entity: " + e.getMessage(), e); - } finally { - session.close(); - } - } -} diff --git a/src/main/java/com/rosetta/im/database/Repository.java b/src/main/java/com/rosetta/im/database/Repository.java new file mode 100644 index 0000000..2d0895f --- /dev/null +++ b/src/main/java/com/rosetta/im/database/Repository.java @@ -0,0 +1,158 @@ +package com.rosetta.im.database; + +import java.util.HashMap; + +import org.hibernate.Session; +import org.hibernate.Transaction; + +/** + * Базовый репозиторий для работы с сущностями базы данных + */ +public abstract class Repository { + + protected Class entityClass; + + public Repository(Class entityClass) { + this.entityClass = entityClass; + } + + public T save(T entity) { + return executeInTransaction(session -> { + session.persist(entity); + return entity; + }); + } + + public T update(T entity) { + return executeInTransaction(session -> { + session.merge(entity); + return entity; + }); + } + + public void delete(T entity) { + executeInTransaction(session -> { + session.remove(entity); + return null; + }); + } + + /** + * Поиск сущности по значению одного поля + * @param fieldName поле + * @param value значение + * @return найденная сущность или null + */ + public T findByField(String fieldName, Object value) { + return executeInSession(session -> { + String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " = :value"; + return session.createQuery(queryString, entityClass) + .setParameter("value", value) + .uniqueResult(); + }); + } + + /** + * Поиск сущности по набору полей + * @param fields карта полей и их значений + * @return найденная сущность или null + */ + public T findByField(HashMap fields) { + return executeInSession(session -> { + StringBuilder queryString = new StringBuilder("FROM " + entityClass.getSimpleName() + " WHERE "); + int index = 0; + for (String fieldName : fields.keySet()) { + if (index > 0) { + queryString.append(" AND "); + } + queryString.append(fieldName).append(" = :").append(fieldName); + index++; + } + var query = session.createQuery(queryString.toString(), this.entityClass); + for (var entry : fields.entrySet()) { + query.setParameter(entry.getKey(), entry.getValue()); + } + return query.uniqueResult(); + }); + } + + public void update(HashMap fieldsToUpdate, HashMap whereFields) { + executeInTransaction(session -> { + StringBuilder queryString = new StringBuilder("UPDATE " + entityClass.getSimpleName() + " SET "); + int index = 0; + for (String fieldName : fieldsToUpdate.keySet()) { + if (index > 0) { + queryString.append(", "); + } + queryString.append(fieldName).append(" = :").append(fieldName); + index++; + } + queryString.append(" WHERE "); + index = 0; + for (String fieldName : whereFields.keySet()) { + if (index > 0) { + queryString.append(" AND "); + } + /** + * Добавляем префикс where_ к параметрам условия WHERE, + * чтобы избежать конфликтов имен с параметрами SET + */ + queryString.append(fieldName).append(" = :where_").append(fieldName); + index++; + } + var query = session.createQuery(queryString.toString(), this.entityClass); + for (var entry : fieldsToUpdate.entrySet()) { + query.setParameter(entry.getKey(), entry.getValue()); + } + for (var entry : whereFields.entrySet()) { + /** + * Устанавливаем параметры с префиксом where_ для условий WHERE, + * чтобы избежать конфликтов имен с параметрами SET + */ + query.setParameter("where_" + entry.getKey(), entry.getValue()); + } + query.executeUpdate(); + return null; + }); + } + + protected R executeInTransaction(TransactionCallback callback) { + Session session = HibernateUtil.openSession(); + Transaction transaction = null; + try { + transaction = session.beginTransaction(); + R result = callback.execute(session); + transaction.commit(); + return result; + } catch (Exception e) { + if (transaction != null) { + transaction.rollback(); + } + throw new RuntimeException("Transaction failed: " + e.getMessage(), e); + } finally { + session.close(); + } + } + + protected R executeInSession(SessionCallback callback) { + try (Session session = HibernateUtil.openSession()) { + return callback.execute(session); + } + } + + + /** + * Функциональный интерфейс для обратного вызова транзакции. + * (Коллбэки) + */ + @FunctionalInterface + protected interface TransactionCallback { + R execute(Session session); + } + + @FunctionalInterface + protected interface SessionCallback { + R execute(Session session); + } + +} diff --git a/src/main/java/com/rosetta/im/database/entity/Device.java b/src/main/java/com/rosetta/im/database/entity/Device.java new file mode 100644 index 0000000..fe40cae --- /dev/null +++ b/src/main/java/com/rosetta/im/database/entity/Device.java @@ -0,0 +1,84 @@ +package com.rosetta.im.database.entity; + +import com.rosetta.im.database.CreateUpdateEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Table; + +@Entity +@Table(name = "devices") +public class Device extends CreateUpdateEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "publicKey", nullable = false) + private String publicKey; + @Column(name = "deviceId", nullable = false) + private String deviceId; + @Column(name = "deviceName", nullable = false) + private String deviceName; + @Column(name = "deviceOs", nullable = false) + private String deviceOs; + /** + * Время завершения сессии устройства + */ + @Column(name = "leaveTime", nullable = true) + private Long leaveTime; + + @PrePersist + protected void onCreate() { + this.leaveTime = 0L; + } + + public Long getId() { + return id; + } + + public String getPublicKey() { + return publicKey; + } + + public String getDeviceId() { + return deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public String getDeviceOs() { + return deviceOs; + } + + public Long getLeaveTime() { + return leaveTime; + } + + public void setLeaveTime(Long leaveTime) { + this.leaveTime = leaveTime; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + 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/database/entity/User.java b/src/main/java/com/rosetta/im/database/entity/User.java index d757604..355ceae 100644 --- a/src/main/java/com/rosetta/im/database/entity/User.java +++ b/src/main/java/com/rosetta/im/database/entity/User.java @@ -1,7 +1,6 @@ package com.rosetta.im.database.entity; -import com.rosetta.im.database.BaseEntity; -import com.rosetta.im.database.HibernateUtil; +import com.rosetta.im.database.CreateUpdateEntity; import com.rosetta.im.database.converters.StringListConverter; import jakarta.persistence.Column; @@ -11,59 +10,40 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Index; -import jakarta.persistence.PrePersist; -import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; -import org.hibernate.Session; -import org.hibernate.Transaction; import java.util.ArrayList; -import java.util.Date; import java.util.List; @Entity @Table(name = "users", indexes = { @Index(name = "idx_users_publickey", columnList = "publicKey", unique = true) }) -public class User extends BaseEntity { +public class User extends CreateUpdateEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "privateKey", nullable = false) - private String privateKey; - @Column(name = "username") private String username; @Column(name = "title") - private String title; + private String title; + + @Column(name = "verified", nullable = false) + private int verified; + + @Column(name = "privateKey", nullable = false) + private String privateKey; @Column(name = "publicKey", nullable = false, unique = true) private String publicKey; - @Column(name = "createdTime", nullable = false, updatable = false) - private Date createdTime; - - @Column(name = "verified", nullable = false) - private int verified; - @Convert(converter = StringListConverter.class) @Column(name = "notificationsTokens", nullable = false) private List notificationsTokens = new ArrayList<>(); - @PrePersist - protected void onCreate() { - createdTime = new Date(); - } - - @PreUpdate - protected void onUpdate() { - if (createdTime == null) { - createdTime = new Date(); - } - } public Long getId() { return id; @@ -101,10 +81,6 @@ public class User extends BaseEntity { this.publicKey = publicKey; } - public Date getCreatedTime() { - return createdTime; - } - public int getVerified() { return verified; } @@ -121,98 +97,4 @@ public class User extends BaseEntity { this.notificationsTokens = notificationsTokens; } - public static User getByPrivateKey(String privateKey) { - try (Session session = HibernateUtil.openSession()) { - return session.createQuery( - "from User where privateKey = :privateKey", User.class) - .setParameter("privateKey", privateKey) - .uniqueResult(); - } - } - - public static User getByUsername(String username) { - try (Session session = HibernateUtil.openSession()) { - return session.createQuery( - "from User where username = :username", User.class) - .setParameter("username", username) - .uniqueResult(); - } - } - - public static User getByPublicKey(String publicKey) { - try (Session session = HibernateUtil.openSession()) { - return session.createQuery( - "from User where publicKey = :publicKey", User.class) - .setParameter("publicKey", publicKey) - .uniqueResult(); - } - } - - public static void addNotificationToken(String privateKey, String token) { - Session session = HibernateUtil.openSession(); - Transaction transaction = null; - try { - transaction = session.beginTransaction(); - User user = session.createQuery( - "from User where privateKey = :privateKey", User.class) - .setParameter("privateKey", privateKey) - .uniqueResult(); - if (user != null) { - if (!user.notificationsTokens.contains(token)) { - user.notificationsTokens.add(token); - session.merge(user); - } - } - transaction.commit(); - } catch (Exception e) { - if (transaction != null) { - transaction.rollback(); - } - throw new RuntimeException("Error adding notification token: " + e.getMessage(), e); - } finally { - session.close(); - } - } - - public static void removeNotificationToken(String privateKey, String token) { - Session session = HibernateUtil.openSession(); - Transaction transaction = null; - try { - transaction = session.beginTransaction(); - User user = session.createQuery( - "from User where privateKey = :privateKey", User.class) - .setParameter("privateKey", privateKey) - .uniqueResult(); - if (user != null) { - user.notificationsTokens = new ArrayList<>(user.notificationsTokens.stream() - .filter(t -> !t.equals(token)) - .toList()); - session.merge(user); - } - transaction.commit(); - } catch (Exception e) { - if (transaction != null) { - transaction.rollback(); - } - throw new RuntimeException("Error removing notification token: " + e.getMessage(), e); - } finally { - session.close(); - } - } - - public static List getNotificationTokensByPrivateKey(String privateKey) { - User user = getByPrivateKey(privateKey); - if (user != null) { - return user.notificationsTokens; - } - return List.of(); - } - - public static List getNotificationsTokensByPublicKey(String publicKey) { - User user = getByPublicKey(publicKey); - if (user != null) { - return user.notificationsTokens; - } - return List.of(); - } } diff --git a/src/main/java/com/rosetta/im/database/repository/DeviceRepository.java b/src/main/java/com/rosetta/im/database/repository/DeviceRepository.java new file mode 100644 index 0000000..fd28cdd --- /dev/null +++ b/src/main/java/com/rosetta/im/database/repository/DeviceRepository.java @@ -0,0 +1,5 @@ +package com.rosetta.im.database.repository; + +public class DeviceRepository { + +} diff --git a/src/main/java/com/rosetta/im/database/repository/UserRepository.java b/src/main/java/com/rosetta/im/database/repository/UserRepository.java new file mode 100644 index 0000000..068a88a --- /dev/null +++ b/src/main/java/com/rosetta/im/database/repository/UserRepository.java @@ -0,0 +1,13 @@ +package com.rosetta.im.database.repository; + +import com.rosetta.im.database.Repository; +import com.rosetta.im.database.entity.User; + +public class UserRepository extends Repository { + + public UserRepository() { + super(User.class); + } + + +} diff --git a/src/main/java/com/rosetta/im/event/events/handshake/BaseHandshakeEvent.java b/src/main/java/com/rosetta/im/event/events/handshake/BaseHandshakeEvent.java new file mode 100644 index 0000000..7506235 --- /dev/null +++ b/src/main/java/com/rosetta/im/event/events/handshake/BaseHandshakeEvent.java @@ -0,0 +1,58 @@ +package com.rosetta.im.event.events.handshake; + +import com.rosetta.im.client.tags.ECIAuthentificate; +import com.rosetta.im.client.tags.ECIDevice; +import com.rosetta.im.event.Cancelable; +import com.rosetta.im.event.Event; + +import io.orprotocol.client.Client; + +/** + * Базовое событие хэндшейка + */ +public class BaseHandshakeEvent extends Event implements Cancelable { + + private String publicKey; + private String privateKey; + private ECIDevice device; + private ECIAuthentificate eciAuthentificate; + private Client client; + private boolean canceled = false; + + public BaseHandshakeEvent(String publicKey, String privateKey, ECIDevice device, ECIAuthentificate eciAuthentificate, Client client) { + this.publicKey = publicKey; + this.privateKey = privateKey; + this.device = device; + this.eciAuthentificate = eciAuthentificate; + this.client = client; + } + + public String getPublicKey() { + return publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public ECIDevice getDevice() { + return device; + } + + public ECIAuthentificate getEciAuthentificate() { + return eciAuthentificate; + } + + public Client getClient() { + return client; + } + + public void setCanceled(boolean canceled) { + this.canceled = canceled; + } + + public boolean isCanceled() { + return canceled; + } + +} diff --git a/src/main/java/com/rosetta/im/event/events/handshake/HandshakeCompletedEvent.java b/src/main/java/com/rosetta/im/event/events/handshake/HandshakeCompletedEvent.java new file mode 100644 index 0000000..60f9cce --- /dev/null +++ b/src/main/java/com/rosetta/im/event/events/handshake/HandshakeCompletedEvent.java @@ -0,0 +1,16 @@ +package com.rosetta.im.event.events.handshake; + + +import com.rosetta.im.client.tags.ECIAuthentificate; +import com.rosetta.im.client.tags.ECIDevice; + +import io.orprotocol.client.Client; + +public class HandshakeCompletedEvent extends BaseHandshakeEvent { + + public HandshakeCompletedEvent(String publicKey, String privateKey, ECIDevice device, ECIAuthentificate eciAuthentificate, Client client) { + super(publicKey, privateKey, device, eciAuthentificate, client); + } + + +} diff --git a/src/main/java/com/rosetta/im/event/events/handshake/HandshakeFailedEvent.java b/src/main/java/com/rosetta/im/event/events/handshake/HandshakeFailedEvent.java new file mode 100644 index 0000000..2f769ae --- /dev/null +++ b/src/main/java/com/rosetta/im/event/events/handshake/HandshakeFailedEvent.java @@ -0,0 +1,15 @@ +package com.rosetta.im.event.events.handshake; + +import com.rosetta.im.client.tags.ECIAuthentificate; +import com.rosetta.im.client.tags.ECIDevice; + +import io.orprotocol.client.Client; + +public class HandshakeFailedEvent extends BaseHandshakeEvent { + + public HandshakeFailedEvent(String publicKey, String privateKey, ECIDevice eciDevice, ECIAuthentificate eciAuthentificate, + Client client) { + super(publicKey, privateKey, eciDevice, eciAuthentificate, client); + } + +} diff --git a/src/main/java/com/rosetta/im/executors/Executor0Handshake.java b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java index 43e58e3..0f0b999 100644 --- a/src/main/java/com/rosetta/im/executors/Executor0Handshake.java +++ b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java @@ -1,35 +1,42 @@ package com.rosetta.im.executors; -import java.util.HashMap; - +import com.rosetta.im.AppContext; 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.client.tags.ECIAuthentificate; +import com.rosetta.im.client.tags.ECIDevice; import com.rosetta.im.database.entity.User; +import com.rosetta.im.database.repository.UserRepository; +import com.rosetta.im.event.events.handshake.HandshakeCompletedEvent; +import com.rosetta.im.event.events.handshake.HandshakeFailedEvent; import com.rosetta.im.packet.Packet0Handshake; import com.rosetta.im.packet.enums.HandshakeStage; +import io.orprotocol.ProtocolException; import io.orprotocol.client.Client; import io.orprotocol.packet.Packet; import io.orprotocol.packet.PacketExecutor; public class Executor0Handshake extends PacketExecutor { + private final UserRepository userRepository = new UserRepository(); + @Override - public void onPacketReceived(Packet packet, Client client) { + public void onPacketReceived(Packet packet, Client client) throws ProtocolException { Packet0Handshake handshake = (Packet0Handshake) packet; String publicKey = handshake.getPublicKey(); String privateKey = handshake.getPrivateKey(); - Device device = handshake.getDevice(); + String deviceId = handshake.getDeviceId(); + String deviceName = handshake.getDeviceName(); + String deviceOs = handshake.getDeviceOs(); int protocolVersion = handshake.getProtocolVersion(); + AppContext context = (AppContext) this.getContext(); /** * Получаем информацию об аутентификации клиента * используя возможности ECI тэгов. */ - Authentificate authentificate = client.getTag(new Authentificate()); + ECIAuthentificate authentificate = client.getTag(ECIAuthentificate.class); if(authentificate != null && authentificate.hasAuthorized()) { /** * Клиент уже авторизован, повторный хэндшейк не допускается @@ -45,12 +52,15 @@ public class Executor0Handshake extends PacketExecutor { return; } + /** + * Создаем минимальную информацию об устройстве клиента + */ + ECIDevice device = new ECIDevice(deviceId, deviceName, deviceOs); + /** * Проверяем есть ли такой пользователь */ - User user = DatabaseManager.findBy(User.class, new HashMap<>() {{ - put("publicKey", publicKey); - }}); + User user = userRepository.findByField("publicKey", publicKey); if(user == null) { /** @@ -66,22 +76,47 @@ public class Executor0Handshake extends PacketExecutor { */ user.setVerified(0); - DatabaseManager.save(user); + userRepository.save(user); - Authentificate eciTag = new Authentificate(device, publicKey, privateKey, HandshakeStage.COMPLETED); + /** + * Ставим метку аутентификации на клиента + */ + ECIAuthentificate eciTag = new ECIAuthentificate + (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); - - + /** + * Вызываем событие завершения хэндшейка + */ + boolean cancelled = context.getEventManager().callEvent( + new HandshakeCompletedEvent(publicKey, privateKey, device, eciTag, client) + ); + if(cancelled) { + /** + * Событие было отменено, не даем завершить хэндшейк + */ + client.disconnect(Failures.DATA_MISSMATCH); + return; + } + /** + * Отправляем клиенту подтверждение успешного хэндшейка + */ + Packet0Handshake response = new Packet0Handshake(); + response.setHandshakeStage(HandshakeStage.COMPLETED); + response.setHeartbeatInterval(this.settings.heartbeatInterval); + client.send(response); + return; + } + /** + * Пользователь найден, проверяем приватный ключ + */ + if(!user.getPrivateKey().equals(privateKey)){ + /** + * Приватный ключ не совпадает, отключаем клиента + */ + context.getEventManager().callEvent(new HandshakeFailedEvent(publicKey, privateKey, device, authentificate, client)); + client.disconnect(Failures.AUTHENTIFICATION_ERROR); return; } - diff --git a/src/main/java/com/rosetta/im/packet/Packet0Handshake.java b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java index 849cbc7..44f7506 100644 --- a/src/main/java/com/rosetta/im/packet/Packet0Handshake.java +++ b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java @@ -1,6 +1,5 @@ package com.rosetta.im.packet; -import com.rosetta.im.client.tags.Device; import com.rosetta.im.packet.enums.HandshakeStage; import io.orprotocol.Stream; @@ -46,7 +45,9 @@ public class Packet0Handshake extends Packet { /** * Минимальная информация об устройстве клиента */ - private Device device; + private String deviceId; + private String deviceName; + private String deviceOs; /** * Стадия рукопожатия @@ -64,9 +65,9 @@ public class Packet0Handshake extends Packet { 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.writeString(this.deviceId); + stream.writeString(this.deviceName); + stream.writeString(this.deviceOs); stream.writeInt8(this.handshakeStage.getCode()); return stream; } @@ -80,7 +81,9 @@ public class Packet0Handshake extends Packet { String deviceId = stream.readString(); String deviceName = stream.readString(); String deviceOs = stream.readString(); - this.device = new Device(deviceId, deviceName, deviceOs); + this.deviceId = deviceId; + this.deviceName = deviceName; + this.deviceOs = deviceOs; this.handshakeStage = HandshakeStage.fromCode( stream.readInt8() ); @@ -102,8 +105,16 @@ public class Packet0Handshake extends Packet { return heartbeatInterval; } - public Device getDevice() { - return device; + public String getDeviceId() { + return deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public String getDeviceOs() { + return deviceOs; } public HandshakeStage getHandshakeStage() { @@ -114,8 +125,16 @@ public class Packet0Handshake extends Packet { this.handshakeStage = handshakeStage; } - public void setDevice(Device device) { - this.device = device; + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public void setDeviceOs(String deviceOs) { + this.deviceOs = deviceOs; } public void setHeartbeatInterval(int heartbeatInterval) { diff --git a/src/main/java/io/orprotocol/Settings.java b/src/main/java/io/orprotocol/Settings.java index b189a68..9e4003a 100644 --- a/src/main/java/io/orprotocol/Settings.java +++ b/src/main/java/io/orprotocol/Settings.java @@ -9,9 +9,9 @@ public class Settings { * Интервал отправки heartbeat пакетов в секундах. * Если клиент не отправляет heartbeat пакеты в течение этого времени, сервер может считать его отключенным. */ - public long heartbeatInterval = 30; + public int heartbeatInterval = 30; - public Settings(int port, long heartbeatInterval) { + public Settings(int port, int heartbeatInterval) { this.port = port; this.heartbeatInterval = heartbeatInterval; } diff --git a/src/main/java/io/orprotocol/client/Client.java b/src/main/java/io/orprotocol/client/Client.java index f995339..bb24b1f 100644 --- a/src/main/java/io/orprotocol/client/Client.java +++ b/src/main/java/io/orprotocol/client/Client.java @@ -1,7 +1,6 @@ package io.orprotocol.client; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; import org.java_websocket.WebSocket; @@ -23,7 +22,7 @@ public class Client { /** * Любые данные, связанные с клиентом. */ - public Set eciTags; + public HashMap, ECITag> eciTags; /** * Интервал отправки heartbeat пакетов в миллисекундах. */ @@ -44,7 +43,7 @@ public class Client { public Client(WebSocket socket, long heartbeatInterval, PacketManager packetManager) { this.socket = socket; this.clientId = StringUtil.randomString(32); - this.eciTags = new HashSet(); + this.eciTags = new HashMap, ECITag>(); this.heartbeatInterval = heartbeatInterval; this.lastHeartbeatTime = System.currentTimeMillis(); this.packetManager = new PacketManager(); @@ -80,7 +79,7 @@ public class Client { * Получает данные, связанные с клиентом. * @return Данные клиента. */ - public Set getEciTags() { + public HashMap, ECITag> getEciTags() { return this.eciTags; } @@ -98,14 +97,14 @@ public class Client { * @param value Значение данных. */ public void addTag(T eciTag) { - this.eciTags.add(eciTag); + this.eciTags.put(eciTag.getClass(), eciTag); } /** * Устанавливает данные клиента. * @param data Данные клиента. */ - public void setTags(Set eciTags) { + public void setTags(HashMap, ECITag> eciTags) { this.eciTags = eciTags; } @@ -114,13 +113,8 @@ public class Client { * @param key Ключ данных. * @return Значение данных. */ - public T getTag(ECITag eciTag) { - for (ECITag tag : this.eciTags) { - if (tag.getKey().equals(eciTag.getKey())) { - return (T) tag; - } - } - return null; + public T getTag(Class eciTagClass) { + return (T) this.eciTags.get(eciTagClass); } /** @@ -128,13 +122,9 @@ public class Client { * @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; + public boolean hasTag(Class eciTagClass) { + return this.eciTags.containsKey(eciTagClass); + } /** diff --git a/src/main/java/io/orprotocol/client/ECITag.java b/src/main/java/io/orprotocol/client/ECITag.java index b050e6f..b8365c1 100644 --- a/src/main/java/io/orprotocol/client/ECITag.java +++ b/src/main/java/io/orprotocol/client/ECITag.java @@ -5,27 +5,4 @@ package io.orprotocol.client; * * Используется для хранения дополнительной информации о клиенте. */ -public abstract class ECITag { - - /** - * Ключ тега. - */ - public String key; - - /** - * Создает новый тег с указанным ключом и значением. - * @param key Ключ тега. - */ - public ECITag(String key) { - this.key = key; - } - - /** - * Получает ключ тега. - * @return Ключ тега. - */ - public String getKey() { - return this.key; - } - -} +public abstract class ECITag {}