Система вложенных тегов (ECI), связь с базой данных
This commit is contained in:
7
src/main/java/com/rosetta/im/Configuration.java
Normal file
7
src/main/java/com/rosetta/im/Configuration.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package com.rosetta.im;
|
||||
|
||||
public class Configuration {
|
||||
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
|
||||
}
|
||||
26
src/main/java/com/rosetta/im/Failures.java
Normal file
26
src/main/java/com/rosetta/im/Failures.java
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class Main {
|
||||
/**
|
||||
* Запуск сервера на порту 8881
|
||||
*/
|
||||
Server server = new Server(settings, manager);
|
||||
Server server = new Server(settings, manager, Configuration.class);
|
||||
server.start();
|
||||
}
|
||||
}
|
||||
68
src/main/java/com/rosetta/im/client/tags/Authentificate.java
Normal file
68
src/main/java/com/rosetta/im/client/tags/Authentificate.java
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.rosetta.im.device;
|
||||
package com.rosetta.im.client.tags;
|
||||
|
||||
public class Device {
|
||||
|
||||
@@ -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> T findBy(Class<T> entity, HashMap<String, Object> 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 <T> void delete(T entity) {
|
||||
Session session = HibernateUtil.openSession();
|
||||
Transaction transaction = null;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
9
src/main/java/com/rosetta/im/protocol/BaseFailures.java
Normal file
9
src/main/java/com/rosetta/im/protocol/BaseFailures.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package com.rosetta.im.protocol;
|
||||
|
||||
public interface BaseFailures {
|
||||
/**
|
||||
* Получает код ошибки.
|
||||
* @return
|
||||
*/
|
||||
int getCode();
|
||||
}
|
||||
@@ -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<String, Object> clientData;
|
||||
/**
|
||||
* Любые данные, связанные с клиентом.
|
||||
*/
|
||||
public Set<ECITag> 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<ECITag>();
|
||||
this.heartbeatInterval = heartbeatInterval;
|
||||
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||
}
|
||||
@@ -68,8 +71,8 @@ public class Client {
|
||||
* Получает данные, связанные с клиентом.
|
||||
* @return Данные клиента.
|
||||
*/
|
||||
public Map<String, Object> getClientData() {
|
||||
return clientData;
|
||||
public Set<ECITag> 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 <T extends ECITag> void addTag(T eciTag) {
|
||||
this.eciTags.add(eciTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает данные клиента.
|
||||
* @param data Данные клиента.
|
||||
*/
|
||||
public void setData(Map<String, Object> data) {
|
||||
this.clientData = data;
|
||||
public void setTags(Set<ECITag> 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 extends ECITag> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
31
src/main/java/com/rosetta/im/protocol/ECITag.java
Normal file
31
src/main/java/com/rosetta/im/protocol/ECITag.java
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<? extends Packet> packetClass = this.packetManager.getPacketClass(packetId);
|
||||
@@ -86,6 +88,8 @@ public class Server extends WebSocketServer {
|
||||
*/
|
||||
Class<? extends PacketExecutor> 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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<hibernate-configuration>
|
||||
<session-factory>
|
||||
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
|
||||
<property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/rosetta_db</property>
|
||||
<property name="hibernate.connection.username">rosetta_user</property>
|
||||
<property name="hibernate.connection.password">rosetta_password</property>
|
||||
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
|
||||
<property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/your_database</property>
|
||||
<property name="hibernate.connection.username">your_user</property>
|
||||
<property name="hibernate.connection.password">your_password</property>
|
||||
|
||||
<property name="hibernate.hbm2ddl.auto">update</property>
|
||||
<property name="hibernate.show_sql">true</property>
|
||||
<property name="hibernate.format_sql">true</property>
|
||||
|
||||
Reference in New Issue
Block a user