Изменение домена с rosetta-im.com на rosetta.im
This commit is contained in:
210
src/main/java/im/rosetta/Boot.java
Normal file
210
src/main/java/im/rosetta/Boot.java
Normal file
@@ -0,0 +1,210 @@
|
||||
package im.rosetta;
|
||||
|
||||
import im.rosetta.client.ClientManager;
|
||||
import im.rosetta.client.OnlineManager;
|
||||
import im.rosetta.event.EventManager;
|
||||
import im.rosetta.executors.Executor0Handshake;
|
||||
import im.rosetta.executors.Executor10RequestUpdate;
|
||||
import im.rosetta.executors.Executor11Typeing;
|
||||
import im.rosetta.executors.Executor15RequestTransport;
|
||||
import im.rosetta.executors.Executor16PushNotification;
|
||||
import im.rosetta.executors.Executor17GroupCreate;
|
||||
import im.rosetta.executors.Executor18GroupInfo;
|
||||
import im.rosetta.executors.Executor19GroupInviteInfo;
|
||||
import im.rosetta.executors.Executor1UserInfo;
|
||||
import im.rosetta.executors.Executor20GroupJoin;
|
||||
import im.rosetta.executors.Executor21GroupLeave;
|
||||
import im.rosetta.executors.Executor22GroupBan;
|
||||
import im.rosetta.executors.Executor24DeviceResolve;
|
||||
import im.rosetta.executors.Executor3Search;
|
||||
import im.rosetta.executors.Executor4OnlineState;
|
||||
import im.rosetta.executors.Executor6Message;
|
||||
import im.rosetta.executors.Executor7Read;
|
||||
import im.rosetta.listeners.DeviceListListener;
|
||||
import im.rosetta.listeners.HandshakeCompleteListener;
|
||||
import im.rosetta.listeners.OnlineStatusDisconnectListener;
|
||||
import im.rosetta.listeners.OnlineStatusHandshakeCompleteListener;
|
||||
import im.rosetta.listeners.ServerStopListener;
|
||||
import im.rosetta.logger.Logger;
|
||||
import im.rosetta.logger.enums.Color;
|
||||
import im.rosetta.logger.enums.LogLevel;
|
||||
import im.rosetta.packet.Packet0Handshake;
|
||||
import im.rosetta.packet.Packet10RequestUpdate;
|
||||
import im.rosetta.packet.Packet11Typeing;
|
||||
import im.rosetta.packet.Packet15RequestTransport;
|
||||
import im.rosetta.packet.Packet16PushNotification;
|
||||
import im.rosetta.packet.Packet17GroupCreate;
|
||||
import im.rosetta.packet.Packet18GroupInfo;
|
||||
import im.rosetta.packet.Packet19GroupInviteInfo;
|
||||
import im.rosetta.packet.Packet1UserInfo;
|
||||
import im.rosetta.packet.Packet20GroupJoin;
|
||||
import im.rosetta.packet.Packet21GroupLeave;
|
||||
import im.rosetta.packet.Packet22GroupBan;
|
||||
import im.rosetta.packet.Packet23DeviceList;
|
||||
import im.rosetta.packet.Packet24DeviceResolve;
|
||||
import im.rosetta.packet.Packet2Result;
|
||||
import im.rosetta.packet.Packet3Search;
|
||||
import im.rosetta.packet.Packet4OnlineSubscribe;
|
||||
import im.rosetta.packet.Packet5OnlineState;
|
||||
import im.rosetta.packet.Packet6Message;
|
||||
import im.rosetta.packet.Packet7Read;
|
||||
import im.rosetta.packet.Packet8Delivery;
|
||||
import im.rosetta.packet.Packet9DeviceNew;
|
||||
|
||||
import io.orprotocol.Server;
|
||||
import io.orprotocol.Settings;
|
||||
import io.orprotocol.packet.PacketManager;
|
||||
|
||||
/**
|
||||
* Boot отвечает за инициализацию всех пакетов и их обработчиков,
|
||||
* а так же событий приложения. Этот Boot отвечает за приложение, а не за протокол.
|
||||
*
|
||||
* Нужен он для того, чтобы все части приложения получали одинаковые ссылки на глобальные обьекты приложения, такие как менеджер пакетов,
|
||||
* менеджер событий, менеджер клиентов и так далее
|
||||
*/
|
||||
public class Boot {
|
||||
|
||||
private PacketManager packetManager;
|
||||
private EventManager eventManager;
|
||||
private Logger logger;
|
||||
private Server server;
|
||||
private ServerAdapter serverAdapter;
|
||||
private ClientManager clientManager;
|
||||
private OnlineManager onlineManager;
|
||||
|
||||
/**
|
||||
* Конструктор по умолчанию, использует порт 3000 для сервера
|
||||
*/
|
||||
public Boot() {
|
||||
this(3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализатор приложения
|
||||
* @param port Порт, на котором будет работать сервер. Если не указан, то будет использован порт 3000
|
||||
*/
|
||||
public Boot(int port) {
|
||||
this.packetManager = new PacketManager();
|
||||
this.eventManager = new EventManager();
|
||||
this.onlineManager = new OnlineManager();
|
||||
this.logger = new Logger(LogLevel.INFO);
|
||||
this.serverAdapter = new ServerAdapter(this.eventManager);
|
||||
this.server = new Server(new Settings(
|
||||
port,
|
||||
30
|
||||
), packetManager, this.serverAdapter);
|
||||
this.clientManager = new ClientManager(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить менеджер пакетов приложения
|
||||
* @return PacketManager
|
||||
*/
|
||||
public PacketManager getPacketManager() {
|
||||
return this.packetManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить менеджер событий приложения
|
||||
* @return EventManager
|
||||
*/
|
||||
public EventManager getEventManager() {
|
||||
return this.eventManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить менеджера клиентов, нужно для того чтобы отправить пакет списку клиентов например
|
||||
* @return менеджер клиентов
|
||||
*/
|
||||
public ClientManager getClientManager() {
|
||||
return this.clientManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить логгер приложения
|
||||
* @return Logger
|
||||
*/
|
||||
public Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запуск сервера, регистрация пакетов, обработчиков, событий приложения
|
||||
* @return Boot
|
||||
*/
|
||||
public Boot bootstrap() {
|
||||
try{
|
||||
this.server.start();
|
||||
this.registerAllPackets();
|
||||
this.registerAllExecutors();
|
||||
this.registerAllEvents();
|
||||
this.printBootMessage();
|
||||
return this;
|
||||
}catch(Exception e){
|
||||
this.logger.error(Color.RED + "Booting error, stack trace:");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerAllEvents() {
|
||||
this.eventManager.registerListener(new ServerStopListener(this.logger));
|
||||
this.eventManager.registerListener(new HandshakeCompleteListener());
|
||||
this.eventManager.registerListener(new OnlineStatusHandshakeCompleteListener(this.onlineManager));
|
||||
this.eventManager.registerListener(new OnlineStatusDisconnectListener(this.onlineManager));
|
||||
this.eventManager.registerListener(new DeviceListListener(this.clientManager));
|
||||
}
|
||||
|
||||
private void registerAllPackets() {
|
||||
this.packetManager.registerPacket(0, Packet0Handshake.class);
|
||||
this.packetManager.registerPacket(1, Packet1UserInfo.class);
|
||||
this.packetManager.registerPacket(2, Packet2Result.class);
|
||||
this.packetManager.registerPacket(3, Packet3Search.class);
|
||||
this.packetManager.registerPacket(4, Packet4OnlineSubscribe.class);
|
||||
this.packetManager.registerPacket(5, Packet5OnlineState.class);
|
||||
this.packetManager.registerPacket(6, Packet6Message.class);
|
||||
this.packetManager.registerPacket(7, Packet7Read.class);
|
||||
this.packetManager.registerPacket(8, Packet8Delivery.class);
|
||||
this.packetManager.registerPacket(9, Packet9DeviceNew.class);
|
||||
this.packetManager.registerPacket(10, Packet10RequestUpdate.class);
|
||||
this.packetManager.registerPacket(11, Packet11Typeing.class);
|
||||
//RESERVED 12 PACKET AVATAR (unused)
|
||||
//RESERVED 13 PACKET KERNEL UPDATE (unused)
|
||||
//RESERVED 14 PACKET APP UPDATE (unused)
|
||||
this.packetManager.registerPacket(15, Packet15RequestTransport.class);
|
||||
this.packetManager.registerPacket(16, Packet16PushNotification.class);
|
||||
this.packetManager.registerPacket(17, Packet17GroupCreate.class);
|
||||
this.packetManager.registerPacket(18, Packet18GroupInfo.class);
|
||||
this.packetManager.registerPacket(19, Packet19GroupInviteInfo.class);
|
||||
this.packetManager.registerPacket(20, Packet20GroupJoin.class);
|
||||
this.packetManager.registerPacket(21, Packet21GroupLeave.class);
|
||||
this.packetManager.registerPacket(22, Packet22GroupBan.class);
|
||||
this.packetManager.registerPacket(23, Packet23DeviceList.class);
|
||||
this.packetManager.registerPacket(24, Packet24DeviceResolve.class);
|
||||
}
|
||||
|
||||
private void registerAllExecutors() {
|
||||
this.packetManager.registerExecutor(0, new Executor0Handshake(this.eventManager, this.clientManager, this.packetManager));
|
||||
this.packetManager.registerExecutor(1, new Executor1UserInfo());
|
||||
this.packetManager.registerExecutor(3, new Executor3Search(this.clientManager));
|
||||
this.packetManager.registerExecutor(4, new Executor4OnlineState(this.onlineManager, this.clientManager));
|
||||
this.packetManager.registerExecutor(6, new Executor6Message(this.clientManager, this.packetManager));
|
||||
this.packetManager.registerExecutor(7, new Executor7Read(this.clientManager, this.packetManager));
|
||||
this.packetManager.registerExecutor(10, new Executor10RequestUpdate());
|
||||
this.packetManager.registerExecutor(11, new Executor11Typeing(this.clientManager, this.packetManager));
|
||||
this.packetManager.registerExecutor(15, new Executor15RequestTransport());
|
||||
this.packetManager.registerExecutor(16, new Executor16PushNotification());
|
||||
this.packetManager.registerExecutor(17, new Executor17GroupCreate());
|
||||
this.packetManager.registerExecutor(18, new Executor18GroupInfo());
|
||||
this.packetManager.registerExecutor(19, new Executor19GroupInviteInfo());
|
||||
this.packetManager.registerExecutor(20, new Executor20GroupJoin());
|
||||
this.packetManager.registerExecutor(21, new Executor21GroupLeave());
|
||||
this.packetManager.registerExecutor(22, new Executor22GroupBan());
|
||||
this.packetManager.registerExecutor(24, new Executor24DeviceResolve(this.clientManager, this.eventManager));
|
||||
}
|
||||
|
||||
private void printBootMessage() {
|
||||
this.logger.log(LogLevel.INFO, Color.GREEN + "Boot successful complete");
|
||||
}
|
||||
|
||||
}
|
||||
47
src/main/java/im/rosetta/Failures.java
Normal file
47
src/main/java/im/rosetta/Failures.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package im.rosetta;
|
||||
|
||||
import io.orprotocol.BaseFailures;
|
||||
|
||||
public enum Failures implements BaseFailures {
|
||||
/**
|
||||
* Ошибка аутентификации
|
||||
*/
|
||||
AUTHENTIFICATION_ERROR(3001),
|
||||
DATA_MISSMATCH(3001),
|
||||
/**
|
||||
* Handshake не завершен
|
||||
*/
|
||||
HANDSHAKE_NOT_COMPLETED(3002),
|
||||
/**
|
||||
* Пользователь не состоит в группе, в которую пытается отправить сообщение
|
||||
*/
|
||||
USER_NOT_IN_GROUP(3005),
|
||||
/**
|
||||
* Неподдерживаемый протокол
|
||||
*/
|
||||
UNSUPPORTED_PROTOCOL(3008),
|
||||
/**
|
||||
* Слишком много вложений отправлено в сообщении
|
||||
*/
|
||||
TOO_MANY_ATTACHMENTS(3009),
|
||||
/**
|
||||
* Слишком много подписок на онлайн статусы
|
||||
*/
|
||||
TOO_MANY_ONLINE_SUBSCRIPTIONS(3010);
|
||||
|
||||
private final int code;
|
||||
|
||||
Failures(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает код ошибки.
|
||||
* @return Код ошибки.
|
||||
*/
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
38
src/main/java/im/rosetta/Main.java
Normal file
38
src/main/java/im/rosetta/Main.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package im.rosetta;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
int port = resolvePort(args);
|
||||
/**
|
||||
* Регистрация всех пакетов и их обработчиков
|
||||
*/
|
||||
Boot boot = new Boot(port);
|
||||
/**
|
||||
* Стартуем сервер
|
||||
*/
|
||||
boot.bootstrap();
|
||||
}
|
||||
|
||||
private static int resolvePort(String[] args) {
|
||||
// Если порт указан аргументом — используем его.
|
||||
if (args != null && args.length > 0) {
|
||||
try {
|
||||
return Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// Если порт задан в окружении — используем его.
|
||||
String envPort = System.getenv("PORT");
|
||||
if (envPort != null && !envPort.isBlank()) {
|
||||
try {
|
||||
return Integer.parseInt(envPort);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// Значение по умолчанию.
|
||||
return 3000;
|
||||
}
|
||||
|
||||
}
|
||||
71
src/main/java/im/rosetta/ServerAdapter.java
Normal file
71
src/main/java/im/rosetta/ServerAdapter.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package im.rosetta;
|
||||
|
||||
import im.rosetta.event.EventManager;
|
||||
import im.rosetta.event.events.ConnectEvent;
|
||||
import im.rosetta.event.events.DisconnectEvent;
|
||||
import im.rosetta.event.events.PacketInputEvent;
|
||||
import im.rosetta.event.events.ServerErrorEvent;
|
||||
import im.rosetta.event.events.ServerStartEvent;
|
||||
import im.rosetta.event.events.ServerStopEvent;
|
||||
|
||||
import io.orprotocol.Server;
|
||||
import io.orprotocol.ServerListener;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Адаптер нужный для трансляции событий протокола в события приложения (EventManager)
|
||||
*/
|
||||
public class ServerAdapter implements ServerListener {
|
||||
|
||||
private EventManager eventManager;
|
||||
|
||||
public ServerAdapter(EventManager eventManager) {
|
||||
this.eventManager = eventManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerStart(Server server) {
|
||||
this.eventManager.callEvent(new ServerStartEvent(server));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerStop(Server server) {
|
||||
this.eventManager.callEvent(new ServerStopEvent(server));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onClientConnect(Server server, Client client) {
|
||||
boolean cancelled = this.eventManager.callEvent(new ConnectEvent(server, client));
|
||||
/**
|
||||
* Если событие отменено (true), то подключение клиента будет отклонено,
|
||||
* иначе (false) клиент будет успешно подключен.
|
||||
* Инверсия нужна для того чтобы соответствовать логике отмены событий в ServerListener,
|
||||
* который требует чтобы мы возвращали true если подключение должно быть разрешено.
|
||||
*/
|
||||
return !cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientDisconnect(Server server, Client client) {
|
||||
this.eventManager.callEvent(new DisconnectEvent(server, client));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Server server, Exception exception) {
|
||||
this.eventManager.callEvent(new ServerErrorEvent(server, exception));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPacketReceived(Server server, Client client, Packet packet) {
|
||||
boolean cancelled = this.eventManager.callEvent(new PacketInputEvent(server, client, packet));
|
||||
/**
|
||||
* Если событие отменено (true), то пакет не будет обработан дальше,
|
||||
* иначе (false) будет продолжена его обработка.
|
||||
* Инверсия нужна для того чтобы соответствовать логике отмены событий в ServerListener,
|
||||
* который требует чтобы мы возвращали true если пакет должен быть обработан дальше.
|
||||
*/
|
||||
return !cancelled;
|
||||
}
|
||||
|
||||
}
|
||||
112
src/main/java/im/rosetta/client/ClientManager.java
Normal file
112
src/main/java/im/rosetta/client/ClientManager.java
Normal file
@@ -0,0 +1,112 @@
|
||||
package im.rosetta.client;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.Server;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.index.ClientIndexer;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Менеджер клиентов
|
||||
*/
|
||||
public class ClientManager {
|
||||
|
||||
private Server server;
|
||||
private ClientIndexer clientIndexer;
|
||||
|
||||
public ClientManager(Server server) {
|
||||
this.server = server;
|
||||
this.clientIndexer = server.getClientIndexer();
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
public boolean isClientConnected(String publicKey) {
|
||||
HashSet<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey);
|
||||
if(clients == null){
|
||||
/**
|
||||
* Нет клиентов с таким публичным ключом
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
if(clients.size() <= 0){
|
||||
/**
|
||||
* Нет клиентов с таким публичным ключом
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Есть клиенты с таким публичным ключом
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправить пакет всем АВТОРИЗОВАННЫМ клиентам с публичным ключом publicKey
|
||||
* @param publicKey публичный ключ получателя
|
||||
* @param packet пакет для отправки
|
||||
* @throws ProtocolException если произошла ошибка при отправке пакета клиенту
|
||||
*/
|
||||
public void sendPacketToAuthorizedPK(String publicKey, Packet packet) throws ProtocolException {
|
||||
HashSet<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey);
|
||||
if(clients == null){
|
||||
/**
|
||||
* Нет клиентов с таким публичным ключом, значит отправлять некому
|
||||
*/
|
||||
return;
|
||||
}
|
||||
for(Client client : clients){
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Если клиент не авторизован, пропускаем его, он не должен получать пакеты,
|
||||
* если нужно отправить пакет неавторизованному клиенту, нужно отправить его напрямую посредством client.send(packet),
|
||||
* а не через этот метод
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Отправляем пакет каждому клиенту с таким публичным ключом (то есть всем его авторизованным сессиям/устройствам)
|
||||
*/
|
||||
client.send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправить пакет всем клиентам с публичными ключами из списка publicKeys
|
||||
* @param publicKeys список публичных ключей получателей
|
||||
* @param packet пакет для отправки
|
||||
* @throws ProtocolException если произошла ошибка при отправке пакета клиенту
|
||||
*/
|
||||
public void sendPacketToAuthorizedPK(List<String> publicKeys, Packet packet) throws ProtocolException {
|
||||
for(String publicKey : publicKeys){
|
||||
this.sendPacketToAuthorizedPK(publicKey, packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить список клиентов по публичному ключу (get PublicKey clients), могут быть неавторизованные клиенты
|
||||
* @param publicKey публичный ключ клиента
|
||||
* @return список клиентов с таким публичным ключом, может быть пустым, если клиентов с таким публичным ключом нет
|
||||
*/
|
||||
public List<Client> getPKClients(String publicKey) {
|
||||
HashSet<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey);
|
||||
if(clients == null){
|
||||
/**
|
||||
* Нет клиентов с таким публичным ключом
|
||||
*/
|
||||
return List.of();
|
||||
}
|
||||
return List.copyOf(clients);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
73
src/main/java/im/rosetta/client/OnlineManager.java
Normal file
73
src/main/java/im/rosetta/client/OnlineManager.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package im.rosetta.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
/**
|
||||
* Отвечает за подписки на онлайн статус пользователей
|
||||
* Каждый пользователь может подписаться на онлайн статус других пользователей
|
||||
* и получать обновления об их статусе в реальном времени
|
||||
*/
|
||||
public class OnlineManager {
|
||||
|
||||
private HashMap<Client, HashSet<String>> onlineSubscriptions;
|
||||
|
||||
public OnlineManager() {
|
||||
this.onlineSubscriptions = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Подписывает клиента на онлайн статус другого пользователя по его публичному ключу
|
||||
* @param client клиент, который подписывается
|
||||
* @param targetPublicKey публичный ключ пользователя, на которого подписываются
|
||||
*/
|
||||
public void subscribe(Client client, String targetPublicKey) {
|
||||
this.onlineSubscriptions.computeIfAbsent(client, k -> new HashSet<>())
|
||||
.add(targetPublicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Отписывает клиента от онлайн статуса другого пользователя по его публичному ключу, например при отключении клиента
|
||||
* @param client клиент, который отписывается от всех (отключается)
|
||||
*/
|
||||
public void unsubscribeAll(Client client) {
|
||||
this.onlineSubscriptions.remove(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает список клиентов, которые подписаны на онлайн статус пользователя с указанным публичным ключом
|
||||
* @param targetPublicKey публичный ключ пользователя, чью онлайн статус интересует
|
||||
* @return список клиентов, подписанных на этот публичный ключ
|
||||
*/
|
||||
public List<Client> getSubscribers(String targetPublicKey) {
|
||||
List<Client> subscribers = new java.util.ArrayList<>();
|
||||
for (var entry : this.onlineSubscriptions.entrySet()) {
|
||||
Client client = entry.getKey();
|
||||
HashSet<String> subscribedKeys = entry.getValue();
|
||||
if (subscribedKeys.contains(targetPublicKey)) {
|
||||
subscribers.add(client);
|
||||
}
|
||||
}
|
||||
return subscribers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает список клиентов, которые подписаны на онлайн статус пользователя, представленного данным клиентом
|
||||
* @param client клиент, представляющий пользователя, чью онлайн статус интересует
|
||||
* @return список клиентов, подписанных на этого пользователя
|
||||
*/
|
||||
public List<Client> getSubscribers(Client client) {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
String publicKey = eciAuthentificate.getPublicKey();
|
||||
return this.getSubscribers(publicKey);
|
||||
}
|
||||
}
|
||||
67
src/main/java/im/rosetta/client/tags/ECIAuthentificate.java
Normal file
67
src/main/java/im/rosetta/client/tags/ECIAuthentificate.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package im.rosetta.client.tags;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import im.rosetta.packet.runtime.HandshakeStage;
|
||||
|
||||
import io.orprotocol.client.ECITag;
|
||||
|
||||
/**
|
||||
* Это вложенный обьект для клиента, содержащий информацию об аутентификации.
|
||||
*/
|
||||
public class ECIAuthentificate implements ECITag {
|
||||
|
||||
public String publicKey;
|
||||
public String privateKey;
|
||||
public HandshakeStage handshakeStage;
|
||||
|
||||
public ECIAuthentificate(String publicKey, String privateKey, HandshakeStage handshakeStage) {
|
||||
this.publicKey = publicKey;
|
||||
this.privateKey = privateKey;
|
||||
this.handshakeStage = handshakeStage;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public HandshakeStage getHandshakeStage() {
|
||||
return handshakeStage;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаем индекс для быстрого поиска клиентов (индексацию реализует ORProtocol)
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getIndex() {
|
||||
Map<String, Object> indexes = new HashMap<>();
|
||||
indexes.put("publicKey", publicKey);
|
||||
return indexes;
|
||||
}
|
||||
|
||||
}
|
||||
41
src/main/java/im/rosetta/client/tags/ECIDevice.java
Normal file
41
src/main/java/im/rosetta/client/tags/ECIDevice.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package im.rosetta.client.tags;
|
||||
|
||||
import io.orprotocol.client.ECITag;
|
||||
|
||||
public class ECIDevice implements ECITag {
|
||||
|
||||
public String deviceId;
|
||||
public String deviceName;
|
||||
public String deviceOs;
|
||||
|
||||
public ECIDevice(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;
|
||||
}
|
||||
|
||||
}
|
||||
42
src/main/java/im/rosetta/database/CreateUpdateEntity.java
Normal file
42
src/main/java/im/rosetta/database/CreateUpdateEntity.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package im.rosetta.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;
|
||||
}
|
||||
|
||||
}
|
||||
46
src/main/java/im/rosetta/database/HibernateUtil.java
Normal file
46
src/main/java/im/rosetta/database/HibernateUtil.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package im.rosetta.database;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
|
||||
public class HibernateUtil {
|
||||
private static final SessionFactory sessionFactory;
|
||||
|
||||
static {
|
||||
try {
|
||||
Configuration cfg = new Configuration().configure();
|
||||
|
||||
String host = System.getenv("DB_HOST");
|
||||
String port = System.getenv("DB_PORT");
|
||||
String user = System.getenv("DB_USER");
|
||||
String pass = System.getenv("DB_PASSWORD");
|
||||
String name = System.getenv("DB_NAME");
|
||||
String url = String.format("jdbc:postgresql://%s:%s/%s", host, port, name);
|
||||
cfg.setProperty("hibernate.connection.url", url);
|
||||
cfg.setProperty("hibernate.connection.username", user);
|
||||
cfg.setProperty("hibernate.connection.password", pass);
|
||||
|
||||
sessionFactory = cfg.buildSessionFactory();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ExceptionInInitializerError("Error initializing Hibernate: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static SessionFactory getSessionFactory() {
|
||||
return sessionFactory;
|
||||
}
|
||||
|
||||
public static Session getCurrentSession() {
|
||||
return sessionFactory.getCurrentSession();
|
||||
}
|
||||
|
||||
public static Session openSession() {
|
||||
return sessionFactory.openSession();
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
sessionFactory.close();
|
||||
}
|
||||
}
|
||||
41
src/main/java/im/rosetta/database/QuerySession.java
Normal file
41
src/main/java/im/rosetta/database/QuerySession.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package im.rosetta.database;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.query.Query;
|
||||
|
||||
public class QuerySession<T> implements AutoCloseable {
|
||||
|
||||
private final Session session;
|
||||
private final Query<T> query;
|
||||
private final Transaction tx;
|
||||
|
||||
public QuerySession(Session session, Query<T> query) {
|
||||
this.session = session;
|
||||
this.query = query;
|
||||
this.tx = session.beginTransaction();
|
||||
}
|
||||
|
||||
public Query<T> getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void commit() {
|
||||
if (tx != null && tx.isActive()) {
|
||||
tx.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
if (tx != null && tx.isActive()) {
|
||||
tx.rollback();
|
||||
}
|
||||
} finally {
|
||||
if (session != null && session.isOpen()) {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
377
src/main/java/im/rosetta/database/Repository.java
Normal file
377
src/main/java/im/rosetta/database/Repository.java
Normal file
@@ -0,0 +1,377 @@
|
||||
package im.rosetta.database;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.query.Query;
|
||||
|
||||
/**
|
||||
* Базовый репозиторий для работы с сущностями базы данных
|
||||
*/
|
||||
public abstract class Repository<T> {
|
||||
|
||||
protected Class<T> entityClass;
|
||||
|
||||
public Repository(Class<T> entityClass) {
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранение сущности в базе данных
|
||||
* @param entity сущность для сохранения
|
||||
* @return сохраненная сущность
|
||||
*/
|
||||
public T save(T entity) {
|
||||
return executeInTransaction(session -> {
|
||||
session.persist(entity);
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление сущности в базе данных
|
||||
* @param entity сущность для обновления
|
||||
* @return обновленная сущность
|
||||
*/
|
||||
public T update(T entity) {
|
||||
return executeInTransaction(session -> {
|
||||
session.merge(entity);
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление сущности из базы данных
|
||||
* @param 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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск сущности по значению одного поля с использованием оператора LIKE
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
* @return найденная сущность или null
|
||||
*/
|
||||
public T likeSearch(String fieldName, String value) {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " LIKE :value";
|
||||
return session.createQuery(queryString, entityClass)
|
||||
.setParameter("value", "%" + value + "%")
|
||||
.uniqueResult();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск сущности по значению одного поля с использованием оператора LIKE
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
* @return найденная сущность или null
|
||||
*/
|
||||
public List<T> likeSearchAll(String fieldName, String value) {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " LIKE :value";
|
||||
return session.createQuery(queryString, entityClass)
|
||||
.setParameter("value", value + "%")
|
||||
.list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск сущности по значению одного поля с использованием оператора LIKE и ограничения LIMIT
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
* @return найденная сущность или null
|
||||
*/
|
||||
public List<T> likeSearchAll(String fieldName, String value, int take) {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " LIKE :value";
|
||||
return session.createQuery(queryString, entityClass)
|
||||
.setParameter("value", value + "%")
|
||||
.setMaxResults(take)
|
||||
.list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск сущности по набору полей
|
||||
* @param fields карта полей и их значений
|
||||
* @return найденная сущность или null
|
||||
*/
|
||||
public T findByField(HashMap<String, Object> 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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление сущностей по значению одного поля
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
*/
|
||||
public void deleteByField(String fieldName, Object value) {
|
||||
executeInTransaction(session -> {
|
||||
String queryString = "DELETE FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " = :value";
|
||||
session.createQuery(queryString, entityClass)
|
||||
.setParameter("value", value)
|
||||
.executeUpdate();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск всех сущностей по значению одного поля
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
* @return список найденных сущностей
|
||||
*/
|
||||
public List<T> findAllByField(String fieldName, Object value) {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " = :value";
|
||||
return session.createQuery(queryString, entityClass)
|
||||
.setParameter("value", value)
|
||||
.list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск всех сущностей по значению набора полей
|
||||
* @param fields карта полей и их значений
|
||||
* @return список найденных сущностей
|
||||
*/
|
||||
public List<T> findAllByField(HashMap<String, Object> 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.list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск всех сущностей, тяжелый метод, лучше не выполнять без необходимости
|
||||
* @return список всех сущностей
|
||||
*/
|
||||
public List<T> findAll() {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "FROM " + entityClass.getSimpleName();
|
||||
return session.createQuery(queryString, entityClass).list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Подсчет всех сущностей в таблице
|
||||
* @return количество сущностей
|
||||
*/
|
||||
public long countAll() {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "SELECT COUNT(*) FROM " + entityClass.getSimpleName();
|
||||
return session.createQuery(queryString, Long.class).uniqueResult();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Подсчет сущностей по значению одного поля
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
* @return количество сущностей
|
||||
*/
|
||||
public long countByField(String fieldName, Object value) {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "SELECT COUNT(*) FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " = :value";
|
||||
return session.createQuery(queryString, Long.class)
|
||||
.setParameter("value", value)
|
||||
.uniqueResult();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет запрос с параметрами и возвращает список сущностей
|
||||
* @param queryString SQL запрос
|
||||
* @param parameters параметры запроса
|
||||
* @param noResultType если true, то не указывать тип результата в запросе, используется для запросов типа UPDATE и DELETE
|
||||
* @return список сущностей
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public QuerySession<T> buildQuery(String queryString, HashMap<String, Object> parameters, boolean noResultType) {
|
||||
Session session = HibernateUtil.openSession();
|
||||
try {
|
||||
Query<T> query;
|
||||
if(noResultType) {
|
||||
query = session.createQuery(queryString);
|
||||
} else {
|
||||
query = session.createQuery(queryString, entityClass);
|
||||
}
|
||||
for (var entry : parameters.entrySet()) {
|
||||
query.setParameter(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return new QuerySession<>(session, query);
|
||||
} catch (Exception e) {
|
||||
session.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет запрос с параметрами и возвращает список сущностей, тип результата указывается автоматически, используется для запросов типа SELECT
|
||||
* @param queryString SQL запрос
|
||||
* @param parameters параметры запроса
|
||||
* @return список сущностей
|
||||
*/
|
||||
public QuerySession<T> buildQuery(String queryString, HashMap<String, Object> parameters) {
|
||||
return buildQuery(queryString, parameters, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Подсчет сущностей по набору полей
|
||||
* @param fields карта полей и их значений
|
||||
* @return количество сущностей
|
||||
*/
|
||||
public long countByField(HashMap<String, Object> fields) {
|
||||
return executeInSession(session -> {
|
||||
StringBuilder queryString = new StringBuilder("SELECT COUNT(*) 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(), Long.class);
|
||||
for (var entry : fields.entrySet()) {
|
||||
query.setParameter(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return query.uniqueResult();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление полей сущности по заданным условиям
|
||||
* @param fieldsToUpdate поля для обновления
|
||||
* @param whereFields условия для выбора сущностей
|
||||
*/
|
||||
public void update(HashMap<String, Object> fieldsToUpdate, HashMap<String, Object> 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> R executeInTransaction(TransactionCallback<R> 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> R executeInSession(SessionCallback<R> callback) {
|
||||
try (Session session = HibernateUtil.openSession()) {
|
||||
return callback.execute(session);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Функциональный интерфейс для обратного вызова транзакции.
|
||||
* (Коллбэки)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
protected interface TransactionCallback<R> {
|
||||
R execute(Session session);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
protected interface SessionCallback<R> {
|
||||
R execute(Session session);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package im.rosetta.database.converters;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
@Converter
|
||||
public class StringListConverter implements AttributeConverter<List<String>, String> {
|
||||
@Override
|
||||
public String convertToDatabaseColumn(List<String> attribute) {
|
||||
if (attribute == null || attribute.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return String.join(",", attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> convertToEntityAttribute(String dbData) {
|
||||
if (dbData == null || dbData.isBlank()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return new ArrayList<>(Arrays.asList(dbData.split(",")));
|
||||
}
|
||||
}
|
||||
85
src/main/java/im/rosetta/database/entity/Buffer.java
Normal file
85
src/main/java/im/rosetta/database/entity/Buffer.java
Normal file
@@ -0,0 +1,85 @@
|
||||
package im.rosetta.database.entity;
|
||||
|
||||
import im.rosetta.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.Table;
|
||||
|
||||
/**
|
||||
* Сущность для буфера сообщений, которые не были доставлены получателю, например,
|
||||
* из-за того, что он был оффлайн, а так же для синхронизации сообщений
|
||||
* между устройствами одного пользователя.
|
||||
* Сообщения в буфере хранятся в виде сериализованных пакетов.
|
||||
* Когда получатель становится онлайн, сервер пытается доставить ему все сообщения из буфера.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "packet_buffer")
|
||||
public class Buffer extends CreateUpdateEntity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "source")
|
||||
private String from;
|
||||
|
||||
@Column(name = "destination")
|
||||
private String to;
|
||||
|
||||
@Column(name = "packetId")
|
||||
private int packetId;
|
||||
|
||||
@Column(name = "packet", columnDefinition = "bytea")
|
||||
private byte[] packet;
|
||||
|
||||
@Column(name = "timestamp")
|
||||
private Long timestamp;
|
||||
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public String getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public byte[] getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setFrom(String from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public void setTo(String to) {
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public void setPacket(byte[] packet) {
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
public void setTimestamp(Long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getPacketId() {
|
||||
return packetId;
|
||||
}
|
||||
|
||||
public void setPacketId(int packetId) {
|
||||
this.packetId = packetId;
|
||||
}
|
||||
}
|
||||
82
src/main/java/im/rosetta/database/entity/Device.java
Normal file
82
src/main/java/im/rosetta/database/entity/Device.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package im.rosetta.database.entity;
|
||||
|
||||
import im.rosetta.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.Index;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "devices", indexes = {
|
||||
@Index(name = "idx_public_key", columnList = "publicKey, deviceId", unique = true)
|
||||
})
|
||||
|
||||
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, columnDefinition = "bigint default 0")
|
||||
private Long leaveTime;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
67
src/main/java/im/rosetta/database/entity/Group.java
Normal file
67
src/main/java/im/rosetta/database/entity/Group.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package im.rosetta.database.entity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.database.CreateUpdateEntity;
|
||||
import im.rosetta.database.converters.StringListConverter;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* Сущность для групповых чатов.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "groups")
|
||||
public class Group extends CreateUpdateEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "groupId")
|
||||
private String groupId;
|
||||
|
||||
@Convert(converter = StringListConverter.class)
|
||||
@Column(name = "membersPublicKeys", nullable = false)
|
||||
private List<String> membersPublicKeys = new ArrayList<>();
|
||||
|
||||
@Convert(converter = StringListConverter.class)
|
||||
@Column(name = "bannedPublicKeys", nullable = false)
|
||||
private List<String> bannedPublicKeys = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public List<String> getMembersPublicKeys() {
|
||||
return membersPublicKeys;
|
||||
}
|
||||
|
||||
public void setMembersPublicKeys(List<String> membersPublicKeys) {
|
||||
this.membersPublicKeys = membersPublicKeys;
|
||||
}
|
||||
|
||||
public List<String> getBannedPublicKeys() {
|
||||
return bannedPublicKeys;
|
||||
}
|
||||
|
||||
public void setBannedPublicKeys(List<String> bannedPublicKeys) {
|
||||
this.bannedPublicKeys = bannedPublicKeys;
|
||||
}
|
||||
|
||||
}
|
||||
100
src/main/java/im/rosetta/database/entity/User.java
Normal file
100
src/main/java/im/rosetta/database/entity/User.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package im.rosetta.database.entity;
|
||||
|
||||
import im.rosetta.database.CreateUpdateEntity;
|
||||
import im.rosetta.database.converters.StringListConverter;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users", indexes = {
|
||||
@Index(name = "idx_users_publickey", columnList = "publicKey", unique = true)
|
||||
})
|
||||
public class User extends CreateUpdateEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "username")
|
||||
private String username;
|
||||
|
||||
@Column(name = "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;
|
||||
|
||||
@Convert(converter = StringListConverter.class)
|
||||
@Column(name = "notificationsTokens", nullable = false)
|
||||
private List<String> notificationsTokens = new ArrayList<>();
|
||||
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public int getVerified() {
|
||||
return verified;
|
||||
}
|
||||
|
||||
public void setVerified(int verified) {
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
public List<String> getNotificationsTokens() {
|
||||
return notificationsTokens;
|
||||
}
|
||||
|
||||
public void setNotificationsTokens(List<String> notificationsTokens) {
|
||||
this.notificationsTokens = notificationsTokens;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package im.rosetta.database.repository;
|
||||
|
||||
import im.rosetta.database.Repository;
|
||||
import im.rosetta.database.entity.Buffer;
|
||||
|
||||
public class BufferRepository extends Repository<Buffer> {
|
||||
|
||||
public BufferRepository() {
|
||||
super(Buffer.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package im.rosetta.database.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.database.Repository;
|
||||
import im.rosetta.database.entity.Device;
|
||||
import im.rosetta.database.entity.User;
|
||||
|
||||
public class DeviceRepository extends Repository<Device> {
|
||||
|
||||
public DeviceRepository() {
|
||||
super(Device.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти все устройства пользователя
|
||||
* @param user пользователь
|
||||
* @return список устройств
|
||||
*/
|
||||
public List<Device> findAll(User user) {
|
||||
return this.findAllByField("publicKey", user.getPublicKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Считает количество устройств пользователя
|
||||
* @param user пользователь
|
||||
* @return количество устройств
|
||||
*/
|
||||
public long countUserDevices(User user) {
|
||||
return this.countByField("publicKey", user.getPublicKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновляет время последней активности устройства
|
||||
* @param deviceId ID устройства
|
||||
*/
|
||||
public void updateDeviceLeaveTime(String deviceId) {
|
||||
Device device = this.findByField("deviceId", deviceId);
|
||||
if(device == null) {
|
||||
return;
|
||||
}
|
||||
device.setLeaveTime(System.currentTimeMillis());
|
||||
this.update(device);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package im.rosetta.database.repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.database.Repository;
|
||||
import im.rosetta.database.entity.Group;
|
||||
|
||||
public class GroupRepository extends Repository<Group> {
|
||||
|
||||
public GroupRepository() {
|
||||
super(Group.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти участников группы по groupId
|
||||
* @param groupId ID группы
|
||||
* @return список публичных ключей участников группы
|
||||
*/
|
||||
public List<String> findGroupMembers(String groupId) {
|
||||
Group group = this.findByField("groupId", groupId);
|
||||
if(group == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return group.getMembersPublicKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать группу с заданным id и создателем, который будет единственным участником группы
|
||||
* @param groupId ID группы
|
||||
* @param creatorPublicKey публичный ключ создателя группы, который будет единственным участником группы при создании
|
||||
*/
|
||||
public void createGroup(String groupId, String creatorPublicKey) {
|
||||
Group group = new Group();
|
||||
group.setGroupId(groupId);
|
||||
List<String> membersPublicKeys = new ArrayList<>();
|
||||
membersPublicKeys.add(creatorPublicKey);
|
||||
group.setMembersPublicKeys(membersPublicKeys);
|
||||
this.save(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить группу по id
|
||||
* @param groupId ID группы
|
||||
* @return группа с заданным id, или null, если группа не найдена
|
||||
*/
|
||||
public Group getGroup(String groupId) {
|
||||
return this.findByField("groupId", groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить группу по id
|
||||
* @param groupId ID группы, которую нужно удалить
|
||||
*/
|
||||
public void removeGroup(String groupId) {
|
||||
Group group = this.findByField("groupId", groupId);
|
||||
if(group != null) {
|
||||
this.delete(group);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить участника в группу
|
||||
* @param groupId ID группы, в которую нужно добавить участника
|
||||
* @param memberPublicKey публичный ключ участника, которого нужно добавить в группу
|
||||
*/
|
||||
public void addMemberToGroup(String groupId, String memberPublicKey) {
|
||||
Group group = this.findByField("groupId", groupId);
|
||||
if(group != null) {
|
||||
List<String> membersPublicKeys = group.getMembersPublicKeys();
|
||||
if(!membersPublicKeys.contains(memberPublicKey)) {
|
||||
membersPublicKeys.add(memberPublicKey);
|
||||
group.setMembersPublicKeys(membersPublicKeys);
|
||||
this.update(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить участника из группы
|
||||
* @param groupId ID группы, из которой нужно удалить участника
|
||||
* @param memberPublicKey публичный ключ участника, которого нужно удалить из группы
|
||||
*/
|
||||
public void removeMemberFromGroup(String groupId, String memberPublicKey) {
|
||||
Group group = this.findByField("groupId", groupId);
|
||||
if(group != null) {
|
||||
List<String> membersPublicKeys = group.getMembersPublicKeys();
|
||||
if(membersPublicKeys.contains(memberPublicKey)) {
|
||||
membersPublicKeys.remove(memberPublicKey);
|
||||
group.setMembersPublicKeys(membersPublicKeys);
|
||||
this.update(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Забанить участника в группе, добавив его публичный ключ в список забаненных публичных ключей группы
|
||||
* @param groupId ID группы, в которой нужно забанить участника
|
||||
* @param memberPublicKey публичный ключ участника, которого нужно забанить в группе
|
||||
*/
|
||||
public void banMemberInGroup(String groupId, String memberPublicKey) {
|
||||
Group group = this.findByField("groupId", groupId);
|
||||
if(group != null) {
|
||||
List<String> bannedPublicKeys = group.getBannedPublicKeys();
|
||||
List<String> membersPublicKeys = group.getMembersPublicKeys();
|
||||
if(membersPublicKeys.contains(memberPublicKey)) {
|
||||
membersPublicKeys.remove(memberPublicKey);
|
||||
group.setMembersPublicKeys(membersPublicKeys);
|
||||
}
|
||||
if(!bannedPublicKeys.contains(memberPublicKey)) {
|
||||
bannedPublicKeys.add(memberPublicKey);
|
||||
group.setBannedPublicKeys(bannedPublicKeys);
|
||||
}
|
||||
this.update(group);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package im.rosetta.database.repository;
|
||||
|
||||
import im.rosetta.database.Repository;
|
||||
import im.rosetta.database.entity.User;
|
||||
|
||||
public class UserRepository extends Repository<User> {
|
||||
|
||||
public UserRepository() {
|
||||
super(User.class);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
17
src/main/java/im/rosetta/event/Cancelable.java
Normal file
17
src/main/java/im/rosetta/event/Cancelable.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package im.rosetta.event;
|
||||
|
||||
public interface Cancelable {
|
||||
|
||||
/**
|
||||
* Отменено ли событие
|
||||
* @return true, если событие отменено
|
||||
*/
|
||||
boolean isCanceled();
|
||||
|
||||
/**
|
||||
* Установить отмену события
|
||||
* @param canceled true, если событие должно быть отменено
|
||||
*/
|
||||
void setCanceled(boolean canceled);
|
||||
|
||||
}
|
||||
18
src/main/java/im/rosetta/event/Event.java
Normal file
18
src/main/java/im/rosetta/event/Event.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package im.rosetta.event;
|
||||
|
||||
public class Event {
|
||||
|
||||
private String name;
|
||||
|
||||
public Event() {}
|
||||
|
||||
public String getEventName() {
|
||||
if(this.name == null) {
|
||||
this.name = this.getClass().getSimpleName();
|
||||
}
|
||||
return this.name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
13
src/main/java/im/rosetta/event/EventException.java
Normal file
13
src/main/java/im/rosetta/event/EventException.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package im.rosetta.event;
|
||||
|
||||
public class EventException extends Exception {
|
||||
|
||||
public EventException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public EventException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
12
src/main/java/im/rosetta/event/EventHandler.java
Normal file
12
src/main/java/im/rosetta/event/EventHandler.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package im.rosetta.event;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EventHandler {
|
||||
|
||||
}
|
||||
119
src/main/java/im/rosetta/event/EventManager.java
Normal file
119
src/main/java/im/rosetta/event/EventManager.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package im.rosetta.event;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Менеджер событий
|
||||
*/
|
||||
public class EventManager {
|
||||
|
||||
private Set<Listener> listeners;
|
||||
|
||||
public EventManager() {
|
||||
this.listeners = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрация слушателя событий
|
||||
* @param listener Слушатель событий
|
||||
*/
|
||||
public void registerListener(Listener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление слушателя событий
|
||||
* @param listener Слушатель событий
|
||||
*/
|
||||
public void unregisterListener(Listener listener) {
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить все зарегистрированные слушатели событий
|
||||
* @return Множество слушателей событий
|
||||
*/
|
||||
public Set<Listener> getListeners() {
|
||||
return this.listeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Вызывает событие и обрабатывает исключения
|
||||
* @param event Событие для вызова
|
||||
* @return true, если событие отменено
|
||||
*/
|
||||
public boolean callEvent(Event event) {
|
||||
try {
|
||||
return fireEvent(event);
|
||||
} catch (EventException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запускает событие, уведомляя всех зарегистрированных слушателей, возвращает true если событие отменено
|
||||
* @param event Событие для запуска
|
||||
* @return true, если событие отменено
|
||||
* @throws EventException Ошибка при обработке события
|
||||
* @throws Exception Общая ошибка при вызове метода
|
||||
*/
|
||||
private boolean fireEvent(Event event) throws EventException {
|
||||
for(Listener listener : this.listeners) {
|
||||
/**
|
||||
* Получаем все методы в Listener с аннотацией @EventHandler
|
||||
*/
|
||||
List<Method> methods = getMethodsWithAnnotation(listener.getClass(), EventHandler.class);
|
||||
for(Method method : methods) {
|
||||
/**
|
||||
* Проверяем, что метод принимает один параметр того же типа, что и событие
|
||||
*/
|
||||
if(method.getParameterCount() == 1
|
||||
&& method.getParameterTypes()[0].isAssignableFrom(event.getClass())) {
|
||||
/**
|
||||
* Если параметры совпадают и они одного типа - вызываем событие
|
||||
*/
|
||||
try{
|
||||
method.setAccessible(true);
|
||||
method.invoke(listener, event);
|
||||
} catch (Exception e) {
|
||||
throw new EventException("Error while invoking event handler method: " + method.getName(), e);
|
||||
}
|
||||
/**
|
||||
* Если событие отменяемое - проверяем его статус
|
||||
*/
|
||||
if(event instanceof Cancelable) {
|
||||
Cancelable cancelableEvent = (Cancelable) event;
|
||||
if(cancelableEvent.isCanceled()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private List<Method> getMethodsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotation) {
|
||||
List<Method> methods = new ArrayList<>();
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (method.isAnnotationPresent(annotation)) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
25
src/main/java/im/rosetta/event/EventPriority.java
Normal file
25
src/main/java/im/rosetta/event/EventPriority.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package im.rosetta.event;
|
||||
|
||||
/**
|
||||
* Приоритет события
|
||||
* Указывает на то, в каком порядке обработаются два одинаковых события
|
||||
*/
|
||||
public enum EventPriority {
|
||||
LOW(0),
|
||||
MEDIUM(1),
|
||||
HIGH(2);
|
||||
|
||||
private final int priority;
|
||||
|
||||
EventPriority(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить приоритет
|
||||
* @return Приоритет события
|
||||
*/
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
}
|
||||
10
src/main/java/im/rosetta/event/Listener.java
Normal file
10
src/main/java/im/rosetta/event/Listener.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package im.rosetta.event;
|
||||
|
||||
/**
|
||||
* Слушатель событий
|
||||
*/
|
||||
public interface Listener {
|
||||
/**
|
||||
* Пустой интерфейс для обозначения слушателя событий
|
||||
*/
|
||||
}
|
||||
41
src/main/java/im/rosetta/event/events/ConnectEvent.java
Normal file
41
src/main/java/im/rosetta/event/events/ConnectEvent.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package im.rosetta.event.events;
|
||||
|
||||
import im.rosetta.event.Cancelable;
|
||||
import im.rosetta.event.Event;
|
||||
|
||||
import io.orprotocol.Server;
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
/**
|
||||
* Событие подключения клиента к серверу.
|
||||
*/
|
||||
public class ConnectEvent extends Event implements Cancelable {
|
||||
|
||||
private boolean canceled = false;
|
||||
private Server server;
|
||||
private Client client;
|
||||
|
||||
public ConnectEvent(Server server, Client client) {
|
||||
this.server = server;
|
||||
this.client = client;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanceled() {
|
||||
return this.canceled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCanceled(boolean canceled) {
|
||||
this.canceled = canceled;
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return this.client;
|
||||
}
|
||||
}
|
||||
29
src/main/java/im/rosetta/event/events/DisconnectEvent.java
Normal file
29
src/main/java/im/rosetta/event/events/DisconnectEvent.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package im.rosetta.event.events;
|
||||
|
||||
import im.rosetta.event.Event;
|
||||
|
||||
import io.orprotocol.Server;
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
/**
|
||||
* Событие отключения клиента от сервера.
|
||||
*/
|
||||
public class DisconnectEvent extends Event {
|
||||
|
||||
private Server server;
|
||||
private Client client;
|
||||
|
||||
public DisconnectEvent(Server server, Client client) {
|
||||
this.server = server;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
}
|
||||
48
src/main/java/im/rosetta/event/events/PacketInputEvent.java
Normal file
48
src/main/java/im/rosetta/event/events/PacketInputEvent.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package im.rosetta.event.events;
|
||||
|
||||
import im.rosetta.event.Cancelable;
|
||||
import im.rosetta.event.Event;
|
||||
|
||||
import io.orprotocol.Server;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Событие входящего пакета от клиента.
|
||||
*/
|
||||
public class PacketInputEvent extends Event implements Cancelable {
|
||||
|
||||
private boolean canceled = false;
|
||||
private Server server;
|
||||
private Client client;
|
||||
private Packet packet;
|
||||
|
||||
public PacketInputEvent(Server server, Client client, Packet packet) {
|
||||
this.server = server;
|
||||
this.client = client;
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanceled() {
|
||||
return this.canceled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCanceled(boolean canceled) {
|
||||
this.canceled = canceled;
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return this.packet;
|
||||
}
|
||||
|
||||
}
|
||||
28
src/main/java/im/rosetta/event/events/ServerErrorEvent.java
Normal file
28
src/main/java/im/rosetta/event/events/ServerErrorEvent.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package im.rosetta.event.events;
|
||||
|
||||
import im.rosetta.event.Event;
|
||||
|
||||
import io.orprotocol.Server;
|
||||
|
||||
/**
|
||||
* Событие ошибки сервера.
|
||||
*/
|
||||
public class ServerErrorEvent extends Event {
|
||||
|
||||
private Exception exception;
|
||||
private Server server;
|
||||
|
||||
public ServerErrorEvent(Server server, Exception exception) {
|
||||
this.server = server;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return this.exception;
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
}
|
||||
22
src/main/java/im/rosetta/event/events/ServerStartEvent.java
Normal file
22
src/main/java/im/rosetta/event/events/ServerStartEvent.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package im.rosetta.event.events;
|
||||
|
||||
import im.rosetta.event.Event;
|
||||
|
||||
import io.orprotocol.Server;
|
||||
|
||||
/**
|
||||
* Событие запуска сервера.
|
||||
*/
|
||||
public class ServerStartEvent extends Event {
|
||||
|
||||
private Server server;
|
||||
|
||||
public ServerStartEvent(Server server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
}
|
||||
22
src/main/java/im/rosetta/event/events/ServerStopEvent.java
Normal file
22
src/main/java/im/rosetta/event/events/ServerStopEvent.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package im.rosetta.event.events;
|
||||
|
||||
import im.rosetta.event.Event;
|
||||
|
||||
import io.orprotocol.Server;
|
||||
|
||||
/**
|
||||
* Событие остановки сервера.
|
||||
*/
|
||||
public class ServerStopEvent extends Event {
|
||||
|
||||
private Server server;
|
||||
|
||||
public ServerStopEvent(Server server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package im.rosetta.event.events.handshake;
|
||||
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.client.tags.ECIDevice;
|
||||
import im.rosetta.event.Cancelable;
|
||||
import im.rosetta.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package im.rosetta.event.events.handshake;
|
||||
|
||||
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package im.rosetta.event.events.handshake;
|
||||
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.client.tags.ECIDevice;
|
||||
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
/**
|
||||
* Вызывается когда устройство клиента нуждается в подтверждении
|
||||
* пользователем с другого устрйоства для завершения хэндшейка.
|
||||
*/
|
||||
public class HandshakeDeviceConfirmEvent extends BaseHandshakeEvent {
|
||||
|
||||
public HandshakeDeviceConfirmEvent(String publicKey, String privateKey, ECIDevice device, ECIAuthentificate eciAuthentificate, Client client) {
|
||||
super(publicKey, privateKey, device, eciAuthentificate, client);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package im.rosetta.event.events.handshake;
|
||||
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package im.rosetta.exception;
|
||||
|
||||
/**
|
||||
* Выбрасывается когда файл конфигурации не найден
|
||||
*/
|
||||
public class ConfigurationException extends Exception {
|
||||
|
||||
public ConfigurationException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package im.rosetta.exception;
|
||||
|
||||
public class UnauthorizedExeception extends Exception {
|
||||
|
||||
public UnauthorizedExeception(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
231
src/main/java/im/rosetta/executors/Executor0Handshake.java
Normal file
231
src/main/java/im/rosetta/executors/Executor0Handshake.java
Normal file
@@ -0,0 +1,231 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.ClientManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.client.tags.ECIDevice;
|
||||
import im.rosetta.database.entity.Device;
|
||||
import im.rosetta.database.entity.User;
|
||||
import im.rosetta.database.repository.BufferRepository;
|
||||
import im.rosetta.database.repository.DeviceRepository;
|
||||
import im.rosetta.database.repository.UserRepository;
|
||||
import im.rosetta.event.EventManager;
|
||||
import im.rosetta.event.events.handshake.HandshakeCompletedEvent;
|
||||
import im.rosetta.event.events.handshake.HandshakeDeviceConfirmEvent;
|
||||
import im.rosetta.event.events.handshake.HandshakeFailedEvent;
|
||||
import im.rosetta.packet.Packet0Handshake;
|
||||
import im.rosetta.packet.Packet9DeviceNew;
|
||||
import im.rosetta.packet.runtime.HandshakeStage;
|
||||
import im.rosetta.service.services.BufferService;
|
||||
import im.rosetta.service.services.DeviceService;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.lock.Lock;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
import io.orprotocol.packet.PacketManager;
|
||||
|
||||
public class Executor0Handshake extends PacketExecutor<Packet0Handshake> {
|
||||
|
||||
private final UserRepository userRepository = new UserRepository();
|
||||
private final DeviceRepository deviceRepository = new DeviceRepository();
|
||||
private final DeviceService deviceService = new DeviceService(deviceRepository);
|
||||
private final EventManager eventManager;
|
||||
private final ClientManager clientManager;
|
||||
private final BufferRepository bufferRepository = new BufferRepository();
|
||||
private final BufferService bufferService;
|
||||
|
||||
public Executor0Handshake(EventManager eventManager, ClientManager clientManager, PacketManager packetManager) {
|
||||
this.eventManager = eventManager;
|
||||
this.clientManager = clientManager;
|
||||
this.bufferService = new BufferService(bufferRepository, packetManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Lock(lockFor = "publicKey")
|
||||
public void onPacketReceived(Packet0Handshake handshake, Client client) throws ProtocolException {
|
||||
String publicKey = handshake.getPublicKey();
|
||||
String privateKey = handshake.getPrivateKey();
|
||||
String deviceId = handshake.getDeviceId();
|
||||
String deviceName = handshake.getDeviceName();
|
||||
String deviceOs = handshake.getDeviceOs();
|
||||
int protocolVersion = handshake.getProtocolVersion();
|
||||
/**
|
||||
* Получаем информацию об аутентификации клиента
|
||||
* используя возможности ECI тэгов.
|
||||
*/
|
||||
ECIAuthentificate authentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(authentificate != null && authentificate.hasAuthorized()) {
|
||||
/**
|
||||
* Клиент уже авторизован, повторный хэндшейк не допускается
|
||||
*/
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Проверяем корректность версии протокола
|
||||
*/
|
||||
if(protocolVersion != 1) {
|
||||
client.disconnect(Failures.UNSUPPORTED_PROTOCOL);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаем минимальную информацию об устройстве клиента
|
||||
*/
|
||||
ECIDevice device = new ECIDevice(deviceId, deviceName, deviceOs);
|
||||
client.addTag(ECIDevice.class, device);
|
||||
|
||||
/**
|
||||
* Проверяем есть ли такой пользователь
|
||||
*/
|
||||
User user = userRepository.findByField("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);
|
||||
|
||||
userRepository.save(user);
|
||||
|
||||
/**
|
||||
* Ставим метку аутентификации на клиента
|
||||
*/
|
||||
ECIAuthentificate eciTag = new ECIAuthentificate
|
||||
(publicKey, privateKey, HandshakeStage.COMPLETED);
|
||||
client.addTag(ECIAuthentificate.class, eciTag);
|
||||
/**
|
||||
* Вызываем событие завершения хэндшейка
|
||||
*/
|
||||
boolean cancelled = this.eventManager.callEvent(
|
||||
new HandshakeCompletedEvent(publicKey, privateKey, device, eciTag, client)
|
||||
);
|
||||
if(cancelled) {
|
||||
/**
|
||||
* Событие было отменено, не даем завершить хэндшейк
|
||||
*/
|
||||
client.disconnect(Failures.DATA_MISSMATCH);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Отправляем клиенту подтверждение успешного хэндшейка
|
||||
*/
|
||||
handshake.setHandshakeStage(HandshakeStage.COMPLETED);
|
||||
handshake.setHeartbeatInterval(this.settings.heartbeatInterval);
|
||||
client.send(handshake);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Пользователь найден, проверяем приватный ключ
|
||||
*/
|
||||
if(!user.getPrivateKey().equals(privateKey)){
|
||||
/**
|
||||
* Приватный ключ не совпадает, отключаем клиента
|
||||
*/
|
||||
eventManager.callEvent(new HandshakeFailedEvent(publicKey, privateKey, device, authentificate, client));
|
||||
client.disconnect(Failures.AUTHENTIFICATION_ERROR);
|
||||
return;
|
||||
}
|
||||
long userDevicesCount = deviceRepository.countUserDevices(user);
|
||||
|
||||
/**
|
||||
* Проверяем верифицировано ли устройство
|
||||
*/
|
||||
if(userDevicesCount > 0 && !deviceService.isDeviceVerifiedByUser(deviceId, user)) {
|
||||
/**
|
||||
* Устройство не верифицировано, нужно отправить клиента
|
||||
* на подтверждение устройства
|
||||
*/
|
||||
handshake.setHandshakeStage(HandshakeStage.NEED_DEVICE_VERIFICATION);
|
||||
handshake.setHeartbeatInterval(this.settings.heartbeatInterval);
|
||||
/**
|
||||
* Ставим метку аутентификации на клиента
|
||||
*/
|
||||
ECIAuthentificate eciTag = new ECIAuthentificate
|
||||
(publicKey, privateKey, HandshakeStage.NEED_DEVICE_VERIFICATION);
|
||||
client.addTag(ECIAuthentificate.class, eciTag);
|
||||
/**
|
||||
* Вызываем событие подтверждения устройства
|
||||
*/
|
||||
this.eventManager.callEvent(
|
||||
new HandshakeDeviceConfirmEvent(publicKey, privateKey, device, authentificate, client)
|
||||
);
|
||||
/**
|
||||
* Отправляем клиенту информацию о необходимости
|
||||
* подтверждения устройства
|
||||
*/
|
||||
client.send(handshake);
|
||||
|
||||
/**
|
||||
* Уведомляем все авторизованные устройства пользователя о том, что нужно подтвердить новое устройство
|
||||
*/
|
||||
Packet9DeviceNew newDevicePacket = new Packet9DeviceNew();
|
||||
newDevicePacket.setDeviceId(deviceId);
|
||||
newDevicePacket.setDeviceName(deviceName);
|
||||
newDevicePacket.setDeviceOs(deviceOs);
|
||||
newDevicePacket.setIpAddress(client.getSocket().getRemoteSocketAddress().getAddress().getHostAddress());
|
||||
clientManager.sendPacketToAuthorizedPK(publicKey, newDevicePacket);
|
||||
/**
|
||||
* Сбрасываем клиенту все старые подтверждения устройств, чтобы исключить спам запросами
|
||||
*/
|
||||
this.bufferService.deletePacketsFromBuffer(publicKey, newDevicePacket, 0);
|
||||
/**
|
||||
* Кладем пакет в очередь на все устройства пользователя,
|
||||
* чтобы если в момент отправки этого пакета какое-то устройство было не онлайн,
|
||||
* то когда оно зайдет в сеть, то получит этот пакет и сможет отреагировать на него,
|
||||
* показав пользователю уведомление о том, что нужно подтвердить новое устройство
|
||||
*/
|
||||
this.bufferService.pushPacketToBuffer("server", publicKey, newDevicePacket);
|
||||
return;
|
||||
}
|
||||
|
||||
if(userDevicesCount == 0) {
|
||||
/**
|
||||
* Это первое устройство пользователя, сохраняем его
|
||||
* как верифицированное
|
||||
*/
|
||||
Device newDevice = new Device();
|
||||
newDevice.setDeviceId(deviceId);
|
||||
newDevice.setDeviceName(deviceName);
|
||||
newDevice.setDeviceOs(deviceOs);
|
||||
newDevice.setPublicKey(publicKey);
|
||||
newDevice.setLeaveTime(System.currentTimeMillis());
|
||||
deviceRepository.save(newDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ставим метку аутентификации на клиента
|
||||
*/
|
||||
ECIAuthentificate eciTag = new ECIAuthentificate
|
||||
(publicKey, privateKey, HandshakeStage.COMPLETED);
|
||||
client.addTag(ECIAuthentificate.class, eciTag);
|
||||
/**
|
||||
* Вызываем событие завершения хэндшейка
|
||||
*/
|
||||
boolean cancelled = this.eventManager.callEvent(
|
||||
new HandshakeCompletedEvent(publicKey, privateKey, device, eciTag, client)
|
||||
);
|
||||
if(cancelled) {
|
||||
/**
|
||||
* Событие было отменено, не даем завершить хэндшейк
|
||||
*/
|
||||
client.disconnect(Failures.DATA_MISSMATCH);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Отправляем клиенту подтверждение успешного хэндшейка
|
||||
*/
|
||||
handshake.setHandshakeStage(HandshakeStage.COMPLETED);
|
||||
handshake.setHeartbeatInterval(this.settings.heartbeatInterval);
|
||||
client.send(handshake);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.packet.Packet10RequestUpdate;
|
||||
import im.rosetta.util.RandomUtil;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
/**
|
||||
* Исполнитель по своей логике идентичен Executor15RequestTransport,
|
||||
* но код продублирован специально, чтобы не размазывать
|
||||
* его например в Dispatcher. Так читать удобнее
|
||||
*/
|
||||
public class Executor10RequestUpdate extends PacketExecutor<Packet10RequestUpdate> {
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet10RequestUpdate packet, Client client) throws Exception, ProtocolException {
|
||||
/**
|
||||
* Обратите внимание этот пакет в отличии от Packet15RequestTransport
|
||||
* не требует авторизации. Это сделано на те случаи когда приложение
|
||||
* обновить нужно, а авторизоваться не получается (например если
|
||||
* авторизация сломалась)
|
||||
*/
|
||||
/**
|
||||
* Если пользователь авторизован, выбираем случайный сервер обновлений и
|
||||
* заполняем им пакет
|
||||
*
|
||||
* TODO: Логика проверки на доступность (health)
|
||||
*/
|
||||
List<String> cdnServers = Arrays.asList(System.getenv("SDU_SERVERS").split(","));
|
||||
String selectedServer = cdnServers.get(RandomUtil.randomBetween(0, cdnServers.size() - 1));
|
||||
packet.setServer(selectedServer);
|
||||
/**
|
||||
* Сервер выбран, отправляем готовый пакет клиенту
|
||||
*/
|
||||
client.send(packet);
|
||||
}
|
||||
|
||||
}
|
||||
73
src/main/java/im/rosetta/executors/Executor11Typeing.java
Normal file
73
src/main/java/im/rosetta/executors/Executor11Typeing.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.ClientManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.packet.Packet11Typeing;
|
||||
import im.rosetta.service.dispatch.MessageDispatcher;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
import io.orprotocol.packet.PacketManager;
|
||||
|
||||
public class Executor11Typeing extends PacketExecutor<Packet11Typeing> {
|
||||
|
||||
private final MessageDispatcher messageDispatcher;
|
||||
|
||||
public Executor11Typeing(ClientManager clientManager, PacketManager packetManager) {
|
||||
this.messageDispatcher = new MessageDispatcher(clientManager, packetManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet11Typeing packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
String fromPublicKey = packet.getFromPublicKey();
|
||||
String toPublicKey = packet.getToPublicKey();
|
||||
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Если пользователь не авторизован он не может отправлять пакет печати
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!eciAuthentificate.getPublicKey().equals(fromPublicKey)){
|
||||
/**
|
||||
* Если клиент пытается отправить сообщение от отправителя,
|
||||
* которым он не является
|
||||
*/
|
||||
client.disconnect(Failures.DATA_MISSMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
if(fromPublicKey.equals(toPublicKey)){
|
||||
/**
|
||||
* Отправка пакета печати самому себе, не кикаем пользователя, так как это ни на что не
|
||||
* влияет, просто ничего не делаем
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляем приватный ключ чтобы не показать его оппоненту
|
||||
*/
|
||||
packet.setPrivateKey("");
|
||||
|
||||
if(toPublicKey.startsWith("#group:")){
|
||||
/**
|
||||
* Пакет печати отправляется в группу, отправляем всем участникам
|
||||
*/
|
||||
this.messageDispatcher.sendGroup(packet, client, eciAuthentificate);
|
||||
}else{
|
||||
/**
|
||||
* Пакет печати отправляется обычному оппоненту (пользователь),
|
||||
* отправляем его, при этом выключаем буфферизацию, потому что пакет печати действителен
|
||||
* только "здесь и сейчас", его не нужно видеть офлайн пользователям
|
||||
*/
|
||||
this.messageDispatcher.sendPeer(packet, client, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.packet.Packet15RequestTransport;
|
||||
import im.rosetta.util.RandomUtil;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor15RequestTransport extends PacketExecutor<Packet15RequestTransport> {
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet15RequestTransport packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Пользователь не авторизован, но запросил транспортный сервер - это неправильное поведение
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Если пользователь авторизован, выбираем случайный транспортный сервер и
|
||||
* заполняем им пакет
|
||||
*
|
||||
* TODO: Логика проверки на доступность (health)
|
||||
*/
|
||||
List<String> cdnServers = Arrays.asList(System.getenv("CDN_SERVERS").split(","));
|
||||
String selectedServer = cdnServers.get(RandomUtil.randomBetween(0, cdnServers.size() - 1));
|
||||
packet.setServer(selectedServer);
|
||||
/**
|
||||
* Сервер выбран, отправляем готовый пакет клиенту
|
||||
*/
|
||||
client.send(packet);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.database.entity.User;
|
||||
import im.rosetta.database.repository.UserRepository;
|
||||
import im.rosetta.packet.Packet16PushNotification;
|
||||
import im.rosetta.service.services.UserService;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor16PushNotification extends PacketExecutor<Packet16PushNotification> {
|
||||
|
||||
private final UserRepository userRepository = new UserRepository();
|
||||
private final UserService userService = new UserService(userRepository);
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet16PushNotification packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Клиент не авторизован, нельзя подписывать его на уведомления
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
String notificationToken = packet.getNotificationToken();
|
||||
if(notificationToken.isEmpty()){
|
||||
/**
|
||||
* Клиент прислал пустой токен, отписывать его от уведомлений не нужно, а подписывать бессмысленно, просто игнорируем этот пакет
|
||||
*/
|
||||
return;
|
||||
}
|
||||
User user = userService.fromClient(client);
|
||||
switch (packet.getAction()) {
|
||||
case SUBSCRIBE:
|
||||
userService.subscribeToPushNotifications(user, notificationToken);
|
||||
break;
|
||||
case UNSUBSCRIBE:
|
||||
userService.unsubscribeFromPushNotifications(user, notificationToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.database.repository.GroupRepository;
|
||||
import im.rosetta.packet.Packet17GroupCreate;
|
||||
import im.rosetta.util.RandomUtil;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor17GroupCreate extends PacketExecutor<Packet17GroupCreate> {
|
||||
|
||||
private final GroupRepository groupRepository = new GroupRepository();
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet17GroupCreate packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Клиент не авторизован, он не может создавать группы
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
String groupId = RandomUtil.randomString(16);
|
||||
this.groupRepository.createGroup(groupId, eciAuthentificate.getPublicKey());
|
||||
/**
|
||||
* Отправляем клиенту ид созданной группы
|
||||
*/
|
||||
packet.setGroupId(groupId);
|
||||
client.send(packet);
|
||||
}
|
||||
|
||||
}
|
||||
58
src/main/java/im/rosetta/executors/Executor18GroupInfo.java
Normal file
58
src/main/java/im/rosetta/executors/Executor18GroupInfo.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.database.entity.Group;
|
||||
import im.rosetta.database.repository.GroupRepository;
|
||||
import im.rosetta.packet.Packet18GroupInfo;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor18GroupInfo extends PacketExecutor<Packet18GroupInfo> {
|
||||
|
||||
private final GroupRepository groupRepository = new GroupRepository();
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet18GroupInfo packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Клиент не авторизован, он не может запрашивать информацию о группах
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
String groupId = packet.getGroupId();
|
||||
Group group = this.groupRepository.getGroup(groupId);
|
||||
if(group == null || group.getMembersPublicKeys().size() <= 0) {
|
||||
/**
|
||||
* Если сервер возвращает пустой список участников,
|
||||
* значит группы не существует, потому что
|
||||
* пустая группа быть не может, так как они автоматически
|
||||
* удаляются при выходе последнего участника
|
||||
*/
|
||||
packet.setMembersPKs(new ArrayList<>());
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
if(!group.getMembersPublicKeys().contains(eciAuthentificate.getPublicKey())){
|
||||
/**
|
||||
* Клиент не является участником группы, значит его может быть
|
||||
* исключили, возвращаем пустую информацию как будто группы нет.
|
||||
*/
|
||||
packet.setMembersPKs(new ArrayList<>());
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Отправляем клиенту список участников группы
|
||||
*/
|
||||
packet.setMembersPKs(group.getMembersPublicKeys());
|
||||
client.send(packet);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.database.entity.Group;
|
||||
import im.rosetta.database.repository.GroupRepository;
|
||||
import im.rosetta.packet.Packet19GroupInviteInfo;
|
||||
import im.rosetta.packet.runtime.NetworkGroupStatus;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor19GroupInviteInfo extends PacketExecutor<Packet19GroupInviteInfo> {
|
||||
|
||||
private final GroupRepository groupRepository = new GroupRepository();
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet19GroupInviteInfo packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Клиент не авторизован, он не может запрашивать информацию о приглашениях
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
String groupId = packet.getGroupId();
|
||||
Group group = this.groupRepository.getGroup(groupId);
|
||||
if(group == null){
|
||||
/**
|
||||
* Группы не существует, возвращаем клиенту статус INVALID
|
||||
*/
|
||||
packet.setStatus(NetworkGroupStatus.INVALID);
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
if(group.getBannedPublicKeys().contains(eciAuthentificate.getPublicKey())){
|
||||
/**
|
||||
* Клиент забанен в группе, возвращаем клиенту статус BANNED
|
||||
*/
|
||||
packet.setStatus(NetworkGroupStatus.BANNED);
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Отправляем клиенту информацию о количестве участников и статусе, является ли
|
||||
* пользователь участником группы или нет
|
||||
*/
|
||||
int membersCount = group.getMembersPublicKeys().size();
|
||||
boolean isMember = group.getMembersPublicKeys().contains(eciAuthentificate.getPublicKey());
|
||||
packet.setMembersCount(membersCount);
|
||||
packet.setStatus(isMember ? NetworkGroupStatus.JOINED : NetworkGroupStatus.NOT_JOINED);
|
||||
client.send(packet);
|
||||
}
|
||||
|
||||
}
|
||||
180
src/main/java/im/rosetta/executors/Executor1UserInfo.java
Normal file
180
src/main/java/im/rosetta/executors/Executor1UserInfo.java
Normal file
@@ -0,0 +1,180 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.database.entity.User;
|
||||
import im.rosetta.database.repository.UserRepository;
|
||||
import im.rosetta.packet.Packet1UserInfo;
|
||||
import im.rosetta.packet.Packet2Result;
|
||||
import im.rosetta.packet.runtime.ResultCode;
|
||||
import im.rosetta.service.services.UserService;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor1UserInfo extends PacketExecutor<Packet1UserInfo> {
|
||||
|
||||
private final UserRepository userRepository = new UserRepository();
|
||||
private final UserService userService = new UserService(userRepository);
|
||||
private final HashSet<String> blockedUsernames = new HashSet<>(Arrays.asList(
|
||||
"user",
|
||||
"admin",
|
||||
"rosettasupport",
|
||||
"rosettaupdates",
|
||||
"freddie871",
|
||||
"updates",
|
||||
"deleted",
|
||||
"safety",
|
||||
"secure",
|
||||
"rosettasafe"
|
||||
));
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet1UserInfo packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
String username = packet.getUsername();
|
||||
String title = packet.getTitle();
|
||||
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Только для авторизованных пользователей, а этот пользователь - не авторизован
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
|
||||
User user = userService.fromClient(client);
|
||||
if(user == null){
|
||||
/**
|
||||
* Пользователь с таким ключем не найден в базе,
|
||||
* такого не может быть, но лучше чтобы была дополнительная проверка
|
||||
*/
|
||||
client.disconnect(Failures.DATA_MISSMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
ResultCode usernameResult = tryChangeUsername(user, username);
|
||||
if(usernameResult == ResultCode.USERNAME_TAKEN){
|
||||
/**
|
||||
* Это имя пользователя уже занято, отправляем клиенту ошибку
|
||||
*/
|
||||
Packet2Result result = new Packet2Result();
|
||||
result.setResultCode(ResultCode.USERNAME_TAKEN);
|
||||
client.send(result);
|
||||
return;
|
||||
}
|
||||
if(usernameResult != ResultCode.SUCCESS){
|
||||
/**
|
||||
* Не удалось сменить username, отправляем клиенту ошибку
|
||||
*/
|
||||
Packet2Result result = new Packet2Result();
|
||||
result.setResultCode(ResultCode.INVALID);
|
||||
client.send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
ResultCode titleResult = tryChangeTitle(user, title);
|
||||
if(titleResult != ResultCode.SUCCESS){
|
||||
/**
|
||||
* Не удалось сменить title, отправляем клиенту ошибку
|
||||
*/
|
||||
Packet2Result result = new Packet2Result();
|
||||
result.setResultCode(ResultCode.INVALID);
|
||||
client.send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправляем клиенту успешный результат
|
||||
*/
|
||||
Packet2Result result = new Packet2Result();
|
||||
result.setResultCode(ResultCode.SUCCESS);
|
||||
client.send(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Пробует сменить username
|
||||
* @param user пользователь
|
||||
* @param username имя пользователя для смены
|
||||
* @return вернет false если смена прошла неудачно или true если username
|
||||
* не нуждается в изменении или изменен
|
||||
*/
|
||||
public ResultCode tryChangeUsername(User user, String username){
|
||||
String targetRegexp = "^[a-z][a-z0-9_]{4,15}$";
|
||||
Pattern pattern = Pattern.compile(targetRegexp);
|
||||
Matcher matcher = pattern.matcher(username);
|
||||
|
||||
if(user.getUsername().equalsIgnoreCase(username)){
|
||||
/**
|
||||
* Пользователь не меняет имя, значит операция прошла успешно,
|
||||
* по крайней мере нам не нужно возвращать клиенту код ошибки
|
||||
*/
|
||||
return ResultCode.SUCCESS;
|
||||
}
|
||||
if(!matcher.matches()){
|
||||
/**
|
||||
* Не подходит по регулярному выражению
|
||||
*/
|
||||
return ResultCode.INVALID;
|
||||
}
|
||||
if(blockedUsernames.contains(username)){
|
||||
/**
|
||||
* Это имя пользователя не доступно для смены
|
||||
*/
|
||||
return ResultCode.INVALID;
|
||||
}
|
||||
if(userService.isUsernameTaken(username)){
|
||||
/**
|
||||
* Такое имя пользователя уже занято
|
||||
*/
|
||||
return ResultCode.USERNAME_TAKEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Меняем имя пользователя
|
||||
*/
|
||||
user.setUsername(username);
|
||||
userRepository.update(user);
|
||||
return ResultCode.SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Пробует сменить заголовок пользователя (title)
|
||||
* @param user пользователь
|
||||
* @param username имя пользователя для смены
|
||||
* @return вернет false если смена прошла неудачно или true если title
|
||||
* не нуждается в изменении или изменен
|
||||
*/
|
||||
public ResultCode tryChangeTitle(User user, String title) {
|
||||
String targetRegexp = "^[a-zA-Z0-9а-яА-Я _-]{1,22}$";
|
||||
Pattern pattern = Pattern.compile(targetRegexp);
|
||||
Matcher matcher = pattern.matcher(title);
|
||||
|
||||
if(user.getTitle().equalsIgnoreCase(title)){
|
||||
/**
|
||||
* Пользователь не меняет имя, значит операция прошла успешно,
|
||||
* по крайней мере нам не нужно возвращать клиенту код ошибки
|
||||
*/
|
||||
return ResultCode.SUCCESS;
|
||||
}
|
||||
if(!matcher.matches()){
|
||||
/**
|
||||
* Не подходит по регулярному выражению
|
||||
*/
|
||||
return ResultCode.INVALID;
|
||||
}
|
||||
/**
|
||||
* Меняем имя пользователя
|
||||
*/
|
||||
user.setTitle(title);
|
||||
userRepository.update(user);
|
||||
return ResultCode.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
65
src/main/java/im/rosetta/executors/Executor20GroupJoin.java
Normal file
65
src/main/java/im/rosetta/executors/Executor20GroupJoin.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.database.entity.Group;
|
||||
import im.rosetta.database.repository.GroupRepository;
|
||||
import im.rosetta.packet.Packet20GroupJoin;
|
||||
import im.rosetta.packet.runtime.NetworkGroupStatus;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor20GroupJoin extends PacketExecutor<Packet20GroupJoin> {
|
||||
|
||||
private final GroupRepository groupRepository = new GroupRepository();
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet20GroupJoin packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Клиент не авторизован, он не может вступать в группы
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
String groupId = packet.getGroupId();
|
||||
Group group = this.groupRepository.getGroup(groupId);
|
||||
if(group == null){
|
||||
/**
|
||||
* Группы не существует, возвращаем клиенту статус INVALID
|
||||
*/
|
||||
packet.setStatus(NetworkGroupStatus.INVALID);
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if(group.getBannedPublicKeys().contains(eciAuthentificate.getPublicKey())){
|
||||
/**
|
||||
* Клиент забанен в группе, возвращаем клиенту статус BANNED
|
||||
*/
|
||||
packet.setStatus(NetworkGroupStatus.BANNED);
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if(group.getMembersPublicKeys().contains(eciAuthentificate.getPublicKey())){
|
||||
/**
|
||||
* Клиент уже является участником группы, возвращаем клиенту статус JOINED
|
||||
*/
|
||||
packet.setStatus(NetworkGroupStatus.JOINED);
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляем клиента в группу и возвращаем клиенту статус JOINED
|
||||
*/
|
||||
this.groupRepository.addMemberToGroup(groupId, eciAuthentificate.getPublicKey());
|
||||
packet.setStatus(NetworkGroupStatus.JOINED);
|
||||
client.send(packet);
|
||||
}
|
||||
|
||||
}
|
||||
66
src/main/java/im/rosetta/executors/Executor21GroupLeave.java
Normal file
66
src/main/java/im/rosetta/executors/Executor21GroupLeave.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.database.entity.Group;
|
||||
import im.rosetta.database.repository.GroupRepository;
|
||||
import im.rosetta.packet.Packet21GroupLeave;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
/**
|
||||
* Обработчик пакета выхода из группы
|
||||
* Отправляет клиенту в ответ такой же пакет, если он успешно покинул группу или не состоял в ней изначально
|
||||
* чтобы клиентское приложение могло корректно обновить интерфейс, например, удалить группу из списка групп пользователя
|
||||
* Если клиент является единственным участником группы, то при выходе группа удаляется целиком
|
||||
*/
|
||||
public class Executor21GroupLeave extends PacketExecutor<Packet21GroupLeave> {
|
||||
|
||||
private final GroupRepository groupRepository = new GroupRepository();
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet21GroupLeave packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Клиент не авторизован, он не может покидать группы
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
String groupId = packet.getGroupId();
|
||||
Group group = this.groupRepository.getGroup(groupId);
|
||||
if(group == null){
|
||||
/**
|
||||
* Группы не существует, просто возвращаем клиенту тот же пакет,
|
||||
* как будто мы успешно покинули группу, потому что по факту мы уже не состоим в ней
|
||||
*/
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
if(!group.getMembersPublicKeys().contains(eciAuthentificate.getPublicKey())){
|
||||
/**
|
||||
* Клиент не является участником группы, просто возвращаем клиенту тот же пакет,
|
||||
* как будто мы успешно покинули группу, потому что по факту мы уже не состоим в ней
|
||||
*/
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
if(group.getMembersPublicKeys().size() <= 1){
|
||||
/**
|
||||
* Клиент является единственным участником группы, удаляем группу целиком
|
||||
*/
|
||||
this.groupRepository.removeGroup(groupId);
|
||||
client.send(packet);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Удаляем клиента из группы
|
||||
*/
|
||||
this.groupRepository.removeMemberFromGroup(groupId, eciAuthentificate.getPublicKey());
|
||||
client.send(packet);
|
||||
}
|
||||
|
||||
}
|
||||
78
src/main/java/im/rosetta/executors/Executor22GroupBan.java
Normal file
78
src/main/java/im/rosetta/executors/Executor22GroupBan.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.database.entity.Group;
|
||||
import im.rosetta.database.repository.GroupRepository;
|
||||
import im.rosetta.packet.Packet18GroupInfo;
|
||||
import im.rosetta.packet.Packet22GroupBan;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor22GroupBan extends PacketExecutor<Packet22GroupBan> {
|
||||
|
||||
private final GroupRepository groupRepository = new GroupRepository();
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet22GroupBan packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Клиент не авторизован, он не может банить участников групп
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
String groupId = packet.getGroupId();
|
||||
String publicKeyToBan = packet.getPublicKey();
|
||||
Group group = this.groupRepository.getGroup(groupId);
|
||||
if(group == null){
|
||||
/**
|
||||
* Группы не существует, но так как клиент вызывает бан участника, предполагается что он админ,
|
||||
* а значит точно должен знать что группы не существует, значит это какое-то атипичное поведение
|
||||
*/
|
||||
client.disconnect(Failures.DATA_MISSMATCH);
|
||||
return;
|
||||
}
|
||||
if(!group.getMembersPublicKeys().get(0).equals(eciAuthentificate.getPublicKey())){
|
||||
/**
|
||||
* Администратор группы - первый участник в списке участников,
|
||||
* если публичный ключ клиента не совпадает с публичным ключом первого участника,
|
||||
* значит он не админ и не может банить участников
|
||||
*/
|
||||
client.disconnect(Failures.DATA_MISSMATCH);
|
||||
return;
|
||||
}
|
||||
if(!group.getMembersPublicKeys().contains(publicKeyToBan)){
|
||||
/**
|
||||
* Пользователя которого пытаются забанить нет в группе
|
||||
*/
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Баним пользователя в группе - удаляем его из участников и добавляем в бан
|
||||
*/
|
||||
this.groupRepository.banMemberInGroup(groupId, publicKeyToBan);
|
||||
/**
|
||||
* Удаляем пользователя из списка участников (сверху мы уже удаляем его в базе,
|
||||
* а здесь просто удаляем из объекта, чтобы отправить
|
||||
* клиенту обновленную информацию о группе)
|
||||
*/
|
||||
List<String> membersPKs = group.getMembersPublicKeys();
|
||||
membersPKs.remove(publicKeyToBan);
|
||||
group.setBannedPublicKeys(membersPKs);
|
||||
/**
|
||||
* Отправляем клиенту новый Packet18GroupInfo, чтобы он обновил информацию о группе,
|
||||
* например, удалил участника из списка участников
|
||||
*/
|
||||
Packet18GroupInfo groupInfoPacket = new Packet18GroupInfo();
|
||||
groupInfoPacket.setGroupId(groupId);
|
||||
groupInfoPacket.setMembersPKs(group.getMembersPublicKeys());
|
||||
client.send(groupInfoPacket);
|
||||
}
|
||||
|
||||
}
|
||||
127
src/main/java/im/rosetta/executors/Executor24DeviceResolve.java
Normal file
127
src/main/java/im/rosetta/executors/Executor24DeviceResolve.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.ClientManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.client.tags.ECIDevice;
|
||||
import im.rosetta.database.entity.Device;
|
||||
import im.rosetta.database.repository.DeviceRepository;
|
||||
import im.rosetta.event.EventManager;
|
||||
import im.rosetta.event.events.handshake.HandshakeCompletedEvent;
|
||||
import im.rosetta.packet.Packet0Handshake;
|
||||
import im.rosetta.packet.Packet24DeviceResolve;
|
||||
import im.rosetta.packet.runtime.DeviceSolution;
|
||||
import im.rosetta.packet.runtime.HandshakeStage;
|
||||
import im.rosetta.service.dispatch.DeviceDispatcher;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor24DeviceResolve extends PacketExecutor<Packet24DeviceResolve> {
|
||||
|
||||
private final ClientManager clientManager;
|
||||
private final EventManager eventManager;
|
||||
private final DeviceRepository deviceRepository = new DeviceRepository();
|
||||
private final DeviceDispatcher deviceDispatcher;
|
||||
|
||||
public Executor24DeviceResolve(ClientManager clientManager, EventManager eventManager) {
|
||||
this.clientManager = clientManager;
|
||||
this.eventManager = eventManager;
|
||||
this.deviceDispatcher = new DeviceDispatcher(clientManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet24DeviceResolve packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if (eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) {
|
||||
/**
|
||||
* Если клиент не прошел аутентификацию, то он не может разрешать устройства
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
String deviceId = packet.getDeviceId();
|
||||
DeviceSolution solution = packet.getSolution();
|
||||
|
||||
/**
|
||||
* Получаем всех клиентов с таким publicKey, и сравниваем внутри них deviceId,
|
||||
* если находим совпадение - разрешаем это устройство
|
||||
*/
|
||||
List<Client> clients = this.clientManager.getPKClients(eciAuthentificate.getPublicKey());
|
||||
for(Client c : clients){
|
||||
ECIDevice deviceTag = c.getTag(ECIDevice.class);
|
||||
if(deviceTag != null && deviceTag.getDeviceId().equals(deviceId)){
|
||||
/**
|
||||
* Нашли клиента с таким deviceId, разрешаем или отклоняем его в зависимости от решения которое
|
||||
* пришло в пакете
|
||||
*/
|
||||
if(solution == DeviceSolution.ACCEPT){
|
||||
/**
|
||||
* Разрешено, запоминаем устройство, инициируем событие успешного хэндшейка, и отправляем успешный хэндшейк этому устройству,
|
||||
* чтобы клиент понял, что устройство разрешено и мог продолжать работу
|
||||
*/
|
||||
Device device = new Device();
|
||||
device.setDeviceId(deviceId);
|
||||
device.setPublicKey(eciAuthentificate.getPublicKey());
|
||||
device.setDeviceOs(deviceTag.getDeviceOs());
|
||||
device.setDeviceName(deviceTag.getDeviceName());
|
||||
/**
|
||||
* TODO: Здесь можно реализовать отключение синхронизации,
|
||||
* например если у пользователя отключена синхронизация, то при разрешении нового устройства
|
||||
* можно устанавливать leaveTime как текущее время, тогда сообщения новому устройству не загрузятся.
|
||||
* Если установить leaveTime в 0, то синхронизируются все сообщения которые есть на сервере
|
||||
*/
|
||||
device.setLeaveTime(0L);
|
||||
this.deviceRepository.save(device);
|
||||
|
||||
/**
|
||||
* Устанавливаем пользователю успешный хэндшейк
|
||||
*/
|
||||
ECIAuthentificate authTag = c.getTag(ECIAuthentificate.class);
|
||||
authTag.setHandshakeStage(HandshakeStage.COMPLETED);
|
||||
c.reindexTag(ECIAuthentificate.class, authTag);
|
||||
/**
|
||||
* Отправляем этому устройству пакет с успешным хэндшейком, чтобы клиент понял,
|
||||
* что устройство разрешено и мог продолжать работу
|
||||
*/
|
||||
Packet0Handshake handshake = new Packet0Handshake();
|
||||
handshake.setHandshakeStage(HandshakeStage.COMPLETED);
|
||||
handshake.setDeviceId("");
|
||||
handshake.setDeviceName("");
|
||||
handshake.setDeviceOs("");
|
||||
handshake.setHeartbeatInterval(this.getSettings().heartbeatInterval);
|
||||
handshake.setPrivateKey("");
|
||||
handshake.setPublicKey("");
|
||||
c.send(handshake);
|
||||
/**
|
||||
* Инициируем событие успешного хэндшейка, чтобы другие части сервера могли отреагировать на это,
|
||||
* например отправить синхронизацию сообщений этому устройству
|
||||
*/
|
||||
this.eventManager.callEvent(new HandshakeCompletedEvent(deviceId, deviceId, deviceTag, eciAuthentificate, client));
|
||||
break;
|
||||
}
|
||||
if(solution == DeviceSolution.DECLINE){
|
||||
/**
|
||||
* Отклонено, отправляем отклонение
|
||||
*/
|
||||
c.send(packet);
|
||||
/**
|
||||
* И удаляем теги аутентификации и устройства, так как клиент в момент отклонения
|
||||
* должен поймать разлогин
|
||||
*/
|
||||
c.clearTags();
|
||||
/**
|
||||
* Отправяем всем устройствам этого пользователя информацию о том, что устройство было отключено (чтобы клиент мог скрыть уведомление
|
||||
* о присоединении нового устройства)
|
||||
*/
|
||||
this.deviceDispatcher.sendDevices(eciAuthentificate.getPublicKey());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
72
src/main/java/im/rosetta/executors/Executor3Search.java
Normal file
72
src/main/java/im/rosetta/executors/Executor3Search.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.ClientManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.database.entity.User;
|
||||
import im.rosetta.database.repository.UserRepository;
|
||||
import im.rosetta.packet.Packet3Search;
|
||||
import im.rosetta.packet.runtime.NetworkStatus;
|
||||
import im.rosetta.packet.runtime.SearchInfo;
|
||||
import im.rosetta.service.services.UserService;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor3Search extends PacketExecutor<Packet3Search> {
|
||||
|
||||
private final UserRepository userRepository = new UserRepository();
|
||||
private final UserService userService = new UserService(userRepository);
|
||||
private final ClientManager clientManager;
|
||||
|
||||
public Executor3Search(ClientManager clientManager) {
|
||||
this.clientManager = clientManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet3Search packet, Client client) throws Exception, ProtocolException {
|
||||
String search = packet.getSearch();
|
||||
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) {
|
||||
/**
|
||||
* Клиент не авторизован, не разрешаем ему выполнять поиск
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
|
||||
if(search.trim().equals("")){
|
||||
/**
|
||||
* Пустой поисковой запрос
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
List<User> usersFindedList = userService.searchUsers(search, 7);
|
||||
Packet3Search response = new Packet3Search();
|
||||
response.setSearch("");
|
||||
|
||||
response.setPrivateKey("");
|
||||
|
||||
List<SearchInfo> searchInfos = new ArrayList<>();
|
||||
for(User user : usersFindedList){
|
||||
SearchInfo searchInfo = new SearchInfo(
|
||||
user.getUsername(),
|
||||
user.getTitle(),
|
||||
user.getPublicKey(),
|
||||
user.getVerified(),
|
||||
NetworkStatus.fromBoolean(this.clientManager.isClientConnected(user.getPublicKey()))
|
||||
);
|
||||
searchInfos.add(searchInfo);
|
||||
}
|
||||
|
||||
response.setSearchInfos(searchInfos);
|
||||
client.send(response);
|
||||
}
|
||||
|
||||
}
|
||||
74
src/main/java/im/rosetta/executors/Executor4OnlineState.java
Normal file
74
src/main/java/im/rosetta/executors/Executor4OnlineState.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.ClientManager;
|
||||
import im.rosetta.client.OnlineManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.packet.Packet4OnlineSubscribe;
|
||||
import im.rosetta.packet.Packet5OnlineState;
|
||||
import im.rosetta.packet.runtime.NetworkStatus;
|
||||
import im.rosetta.packet.runtime.PKNetworkStatus;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor4OnlineState extends PacketExecutor<Packet4OnlineSubscribe> {
|
||||
|
||||
private final OnlineManager onlineManager;
|
||||
private final ClientManager clientManager;
|
||||
|
||||
public Executor4OnlineState(OnlineManager onlineManager, ClientManager clientManager) {
|
||||
this.onlineManager = onlineManager;
|
||||
this.clientManager = clientManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet4OnlineSubscribe packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) {
|
||||
/**
|
||||
* Клиент не авторизован
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Устанавливаем подписку на онлайн статус указанных публичных ключей
|
||||
*/
|
||||
List<String> publicKeys = packet.getPublicKeys();
|
||||
if(publicKeys == null || publicKeys.isEmpty()) {
|
||||
/**
|
||||
* Пустой список, ничего не делаем
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if(publicKeys.size() > 20) {
|
||||
/**
|
||||
* Слишком много подписок за один раз
|
||||
*/
|
||||
client.disconnect(Failures.TOO_MANY_ONLINE_SUBSCRIPTIONS);
|
||||
return;
|
||||
}
|
||||
for (String targetPublicKey : publicKeys) {
|
||||
this.onlineManager.subscribe(client, targetPublicKey);
|
||||
}
|
||||
/**
|
||||
* Сразу же формируем и отправляем клиенту онлайн статус для указанных публичных ключей, чтобы клиент не ждал обновления статуса,
|
||||
* а получил его сразу после подписки
|
||||
*/
|
||||
Packet5OnlineState onlineStates = new Packet5OnlineState();
|
||||
List<PKNetworkStatus> onlineStatuses = new ArrayList<>();
|
||||
for (String targetPublicKey : publicKeys) {
|
||||
boolean isOnline = this.clientManager.isClientConnected(targetPublicKey);
|
||||
PKNetworkStatus networkStatus = new PKNetworkStatus(targetPublicKey, NetworkStatus.fromBoolean(isOnline));
|
||||
onlineStatuses.add(networkStatus);
|
||||
}
|
||||
onlineStates.setPkNetworkStatuses(onlineStatuses);
|
||||
client.send(onlineStates);
|
||||
}
|
||||
|
||||
}
|
||||
126
src/main/java/im/rosetta/executors/Executor6Message.java
Normal file
126
src/main/java/im/rosetta/executors/Executor6Message.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.ClientManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.packet.Packet6Message;
|
||||
import im.rosetta.packet.Packet8Delivery;
|
||||
import im.rosetta.packet.runtime.Attachment;
|
||||
import im.rosetta.service.dispatch.MessageDispatcher;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
import io.orprotocol.packet.PacketManager;
|
||||
|
||||
/**
|
||||
* Обработчик пакета сообщений
|
||||
*/
|
||||
public class Executor6Message extends PacketExecutor<Packet6Message> {
|
||||
|
||||
private final MessageDispatcher messageDispatcher;
|
||||
|
||||
public Executor6Message(ClientManager clientManager, PacketManager packetManager) {
|
||||
this.messageDispatcher = new MessageDispatcher(clientManager, packetManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet6Message packet, Client client) throws Exception, ProtocolException {
|
||||
String fromPublicKey = packet.getFromPublicKey();
|
||||
String toPublicKey = packet.getToPublicKey();
|
||||
String messageId = packet.getMessageId();
|
||||
List<Attachment> attachments = packet.getAttachments();
|
||||
int attachmentsCount = attachments.size();
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Если пользователь не авторизован
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!eciAuthentificate.getPublicKey().equals(fromPublicKey)){
|
||||
/**
|
||||
* Если клиент пытается отправить сообщение от отправителя,
|
||||
* которым он не является
|
||||
*/
|
||||
client.disconnect(Failures.DATA_MISSMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
if(fromPublicKey.equals(toPublicKey)){
|
||||
/**
|
||||
* Самому себе отправить сообщение нельзя, но это более-менее
|
||||
* нормальное поведение, хотя на клиентах должно быть отработано,
|
||||
* не кикаем пользователя
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
long currentTimestampSec = (System.currentTimeMillis() / 1000);
|
||||
long messageTimestampSec = (packet.getTimestamp() / 1000);
|
||||
/**
|
||||
* Максимальный возраст сообщения в секундах, который сервер примет, чтобы
|
||||
* клиент не мог подделать дату отправки и отправлять
|
||||
* сообщения из "прошлого"
|
||||
*/
|
||||
long maxPaddingSec = 30;
|
||||
if(attachmentsCount > 0){
|
||||
/**
|
||||
* Так как у нас есть вложения, то клиенту нужно какое-то время на их загрузку,
|
||||
* разрешаем клиенту превысить maxPaddingSec и даем ему 30 секунд
|
||||
* на отправку одного вложения (этого более чем достаточно, так как клиент
|
||||
* вообще не должен отправлять сообщение пока все вложения не будут загружены
|
||||
* на сервер)
|
||||
*/
|
||||
maxPaddingSec = maxPaddingSec * attachmentsCount;
|
||||
}
|
||||
if(currentTimestampSec - messageTimestampSec > maxPaddingSec){
|
||||
/**
|
||||
* Если сообщение было отправлено из "прошлого", то есть на момент
|
||||
* прихода на сервер сообщению уже больше секунд чем допускает
|
||||
* maxPaddingSec, то отклоняем его
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if(attachmentsCount > 10){
|
||||
/**
|
||||
* Слишком много отправляемых вложений, так нельзя
|
||||
*/
|
||||
client.disconnect(Failures.TOO_MANY_ATTACHMENTS);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Обновляем системную метку времени в соотвествии с сервером,
|
||||
* так как у клиентов могут быть например неправильно настроены часы
|
||||
* или разные часовые пояса
|
||||
*/
|
||||
packet.setTimestamp(System.currentTimeMillis());
|
||||
packet.setPrivateKey("");
|
||||
|
||||
if(toPublicKey.startsWith("#group:")){
|
||||
/**
|
||||
* Это групповое сообщение, отправляем его всем участникам группы, кроме отправителя
|
||||
*/
|
||||
this.messageDispatcher.sendGroup(packet, client, eciAuthentificate);
|
||||
}else{
|
||||
/**
|
||||
* Это личное сообщение, отправляем его получателю
|
||||
*/
|
||||
this.messageDispatcher.sendPeer(packet, client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сообщение успешно отправлено, отправялем клиенту пакет успешной доставки
|
||||
*/
|
||||
Packet8Delivery deliveryPacket = new Packet8Delivery();
|
||||
deliveryPacket.setMessageId(messageId);
|
||||
deliveryPacket.setToPublicKey(toPublicKey);
|
||||
client.send(deliveryPacket);
|
||||
}
|
||||
|
||||
}
|
||||
56
src/main/java/im/rosetta/executors/Executor7Read.java
Normal file
56
src/main/java/im/rosetta/executors/Executor7Read.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package im.rosetta.executors;
|
||||
|
||||
import im.rosetta.Failures;
|
||||
import im.rosetta.client.ClientManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.packet.Packet7Read;
|
||||
import im.rosetta.service.dispatch.MessageDispatcher;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
import io.orprotocol.packet.PacketManager;
|
||||
|
||||
public class Executor7Read extends PacketExecutor<Packet7Read> {
|
||||
|
||||
private final MessageDispatcher messageDispatcher;
|
||||
|
||||
public Executor7Read(ClientManager clientManager, PacketManager packetManager) {
|
||||
this.messageDispatcher = new MessageDispatcher(clientManager, packetManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet7Read packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
String fromPublicKey = packet.getFromPublicKey();
|
||||
String toPublicKey = packet.getToPublicKey();
|
||||
if(fromPublicKey.equals(toPublicKey)){
|
||||
/**
|
||||
* Ничего не делаем если назначение пакета такое же как и отправитель,
|
||||
* такое поведение может быть при заходе в Saved Messages и должно быть правильно обработано на клиенте
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if (eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) {
|
||||
/**
|
||||
* Если клиент не прошел аутентификацию, то он не может читать сообщения
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
packet.setPrivateKey("");
|
||||
|
||||
if(toPublicKey.startsWith("#group:")){
|
||||
/**
|
||||
* Это групповое чтение, отправляем его всем участникам группы, кроме отправителя
|
||||
*/
|
||||
this.messageDispatcher.sendGroup(packet, client, eciAuthentificate);
|
||||
}else{
|
||||
/**
|
||||
* Это личное сообщение, отправляем его получателю
|
||||
*/
|
||||
this.messageDispatcher.sendPeer(packet, client);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package im.rosetta.executors.base;
|
||||
|
||||
public class ExecutorBaseDialog {
|
||||
|
||||
}
|
||||
59
src/main/java/im/rosetta/listeners/DeviceListListener.java
Normal file
59
src/main/java/im/rosetta/listeners/DeviceListListener.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package im.rosetta.listeners;
|
||||
|
||||
import im.rosetta.client.ClientManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.event.EventHandler;
|
||||
import im.rosetta.event.Listener;
|
||||
import im.rosetta.event.events.DisconnectEvent;
|
||||
import im.rosetta.event.events.handshake.HandshakeCompletedEvent;
|
||||
import im.rosetta.event.events.handshake.HandshakeDeviceConfirmEvent;
|
||||
import im.rosetta.service.dispatch.DeviceDispatcher;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
public class DeviceListListener implements Listener {
|
||||
|
||||
private final DeviceDispatcher deviceDispatcher;
|
||||
|
||||
public DeviceListListener(ClientManager clientManager) {
|
||||
this.deviceDispatcher = new DeviceDispatcher(clientManager);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onHandshakeComplete(HandshakeCompletedEvent event) throws ProtocolException {
|
||||
Client client = event.getClient();
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate != null){
|
||||
/**
|
||||
* Когда клиент прошел аутентификацию, отправляем ему список устройств
|
||||
*/
|
||||
this.deviceDispatcher.sendDevices(eciAuthentificate.getPublicKey());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDeviceConfirm(HandshakeDeviceConfirmEvent event) throws ProtocolException {
|
||||
Client client = event.getClient();
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate != null){
|
||||
/**
|
||||
* Когда к аккаунту присоединяется новое устройство отправляем всем клиентам с этим публичным ключом обновленный список устройств
|
||||
*/
|
||||
this.deviceDispatcher.sendDevices(eciAuthentificate.getPublicKey());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDisconnect(DisconnectEvent event) throws ProtocolException {
|
||||
Client client = event.getClient();
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate != null){
|
||||
/**
|
||||
* Когда устройство отключается от аккаунта, отправляем всем клиентам с этим публичным ключом обновленный список устройств
|
||||
*/
|
||||
this.deviceDispatcher.sendDevices(eciAuthentificate.getPublicKey());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package im.rosetta.listeners;
|
||||
|
||||
import im.rosetta.event.Listener;
|
||||
import im.rosetta.event.events.handshake.HandshakeCompletedEvent;
|
||||
|
||||
public class HandshakeCompleteListener implements Listener {
|
||||
|
||||
public void onHandshakeComplete(HandshakeCompletedEvent event) {
|
||||
//TODO: Обработка завершения Handshake и синхронизация недоставленных сообщений (переписок)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package im.rosetta.listeners;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.client.OnlineManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.event.EventHandler;
|
||||
import im.rosetta.event.Listener;
|
||||
import im.rosetta.event.events.DisconnectEvent;
|
||||
import im.rosetta.packet.Packet5OnlineState;
|
||||
import im.rosetta.packet.runtime.NetworkStatus;
|
||||
import im.rosetta.packet.runtime.PKNetworkStatus;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
/**
|
||||
* Слушатель отключения клиента
|
||||
* Нужен для того чтобы обновлять онлайн статус пользоватеей, и уведомлять всех
|
||||
* подписчиков об изменении статуса
|
||||
*/
|
||||
public class OnlineStatusDisconnectListener implements Listener {
|
||||
|
||||
private OnlineManager onlineManager;
|
||||
|
||||
public OnlineStatusDisconnectListener(OnlineManager onlineManager) {
|
||||
this.onlineManager = onlineManager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onClientDisconnect(DisconnectEvent event) throws ProtocolException {
|
||||
Client client = event.getClient();
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) {
|
||||
/**
|
||||
* Клиент не авторизован, ничего не делаем
|
||||
*/
|
||||
return;
|
||||
}
|
||||
List<Client> subscribers = this.onlineManager.getSubscribers(client);
|
||||
/**
|
||||
* Уведомляем всех подписчиков на его онлайн статус, что он отключился (ушел в оффлайн)
|
||||
*/
|
||||
for (Client subscriber : subscribers) {
|
||||
Packet5OnlineState packet = new Packet5OnlineState();
|
||||
List<PKNetworkStatus> statuses = new ArrayList<>();
|
||||
statuses.add(new PKNetworkStatus(
|
||||
eciAuthentificate.getPublicKey(),
|
||||
NetworkStatus.OFFLINE
|
||||
));
|
||||
packet.setPkNetworkStatuses(statuses);
|
||||
subscriber.send(packet);
|
||||
}
|
||||
/**
|
||||
* Удаляем все подписки этого клиента, так как он отключился
|
||||
*/
|
||||
this.onlineManager.unsubscribeAll(client);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package im.rosetta.listeners;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.client.OnlineManager;
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.event.EventHandler;
|
||||
import im.rosetta.event.Listener;
|
||||
import im.rosetta.event.events.handshake.HandshakeCompletedEvent;
|
||||
import im.rosetta.packet.Packet5OnlineState;
|
||||
import im.rosetta.packet.runtime.NetworkStatus;
|
||||
import im.rosetta.packet.runtime.PKNetworkStatus;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
/**
|
||||
* Слушатель завершения рукопожатия (хэндшейкапа) клиента
|
||||
* Нужен для того чтобы обновлять онлайн статус пользоватеей, и уведомлять всех
|
||||
* подписчиков об изменении статуса
|
||||
*/
|
||||
public class OnlineStatusHandshakeCompleteListener implements Listener {
|
||||
|
||||
private final OnlineManager onlineManager;
|
||||
|
||||
public OnlineStatusHandshakeCompleteListener(OnlineManager onlineManager) {
|
||||
this.onlineManager = onlineManager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onHandshakeComplete(HandshakeCompletedEvent event) throws ProtocolException {
|
||||
Client client = event.getClient();
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) {
|
||||
/**
|
||||
* Клиент не авторизован, ничего не делаем, однако такое
|
||||
* не должно происходить, так как событие хэндшейкапа
|
||||
* должно означать что клиент авторизован
|
||||
*/
|
||||
return;
|
||||
}
|
||||
List<Client> subscribers = this.onlineManager.getSubscribers(client);
|
||||
/**
|
||||
* Уведомляем всех подписчиков на его онлайн статус, что он подключился (стал онлайн)
|
||||
*/
|
||||
for (Client subscriber : subscribers) {
|
||||
Packet5OnlineState packet = new Packet5OnlineState();
|
||||
List<PKNetworkStatus> statuses = new ArrayList<>();
|
||||
statuses.add(new PKNetworkStatus(
|
||||
eciAuthentificate.getPublicKey(),
|
||||
NetworkStatus.ONLINE
|
||||
));
|
||||
packet.setPkNetworkStatuses(statuses);
|
||||
subscriber.send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
55
src/main/java/im/rosetta/listeners/ServerStopListener.java
Normal file
55
src/main/java/im/rosetta/listeners/ServerStopListener.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package im.rosetta.listeners;
|
||||
|
||||
import im.rosetta.client.tags.ECIAuthentificate;
|
||||
import im.rosetta.client.tags.ECIDevice;
|
||||
import im.rosetta.database.repository.DeviceRepository;
|
||||
import im.rosetta.event.EventHandler;
|
||||
import im.rosetta.event.Listener;
|
||||
import im.rosetta.event.events.ServerStopEvent;
|
||||
import im.rosetta.logger.Logger;
|
||||
import im.rosetta.logger.enums.Color;
|
||||
|
||||
import io.orprotocol.Server;
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
/**
|
||||
* При остановке сервера нам нужно обновить всем клиентам время последней активности устройства
|
||||
* чтобы корректно потом отработать загрузку сообщений для пользователя
|
||||
*/
|
||||
public class ServerStopListener implements Listener {
|
||||
|
||||
private final DeviceRepository deviceRepository = new DeviceRepository();
|
||||
private Logger logger;
|
||||
|
||||
public ServerStopListener(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onServerStop(ServerStopEvent event) {
|
||||
Server server = event.getServer();
|
||||
this.logger.info(Color.RED + "Сервер останавливается, обновляем время последней активности устройств клиентов...");
|
||||
for(Client client : server.getClients()){
|
||||
ECIAuthentificate eciAuth = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuth == null || !eciAuth.hasAuthorized()){
|
||||
/**
|
||||
* Если клиент не авторизован, пропускаем его, таким клиентам не нужно
|
||||
* обновлять время активности устройства
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
ECIDevice eciDevice = client.getTag(ECIDevice.class);
|
||||
if(eciDevice == null){
|
||||
/**
|
||||
* Если у клиента нет тега устройства, пропускаем его
|
||||
* такого быть не должно, но на всякий случай
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
deviceRepository.updateDeviceLeaveTime(eciDevice.getDeviceId());
|
||||
}
|
||||
this.logger.info(Color.RED + "Время последней активности устройств клиентов обновлено.");
|
||||
}
|
||||
|
||||
}
|
||||
74
src/main/java/im/rosetta/logger/Logger.java
Normal file
74
src/main/java/im/rosetta/logger/Logger.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package im.rosetta.logger;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import im.rosetta.logger.enums.Color;
|
||||
import im.rosetta.logger.enums.LogLevel;
|
||||
|
||||
public class Logger {
|
||||
|
||||
private long startTime = 0;
|
||||
private LogLevel logLevel;
|
||||
|
||||
public Logger(LogLevel logLevel) {
|
||||
this.logLevel = logLevel;
|
||||
startTime = (System.currentTimeMillis() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование сообщения с указанным уровнем логирования
|
||||
* @param logLevel уровень логирования
|
||||
* @param message сообщение для логирования
|
||||
*/
|
||||
public void log(LogLevel logLevel, String message) {
|
||||
if (!this.logLevel.allows(logLevel)) {
|
||||
return;
|
||||
}
|
||||
long currentTimeMs = System.currentTimeMillis();
|
||||
long currentTime = currentTimeMs / 1000;
|
||||
long elapsedTime = currentTime - startTime;
|
||||
String currentDateISO = Instant.ofEpochMilli(currentTimeMs).toString();
|
||||
System.out.println(getColorForLogLevel(logLevel) + "["+ logLevel.toString() +"]" + Color.RESET + "[" + currentDateISO + "]" + Color.CYAN + "[+" + elapsedTime + "] " + Color.WHITE + message + Color.RESET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование информационного сообщения
|
||||
* @param message сообщение для логирования
|
||||
*/
|
||||
public void info(String message) {
|
||||
this.log(LogLevel.INFO, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование предупреждающего сообщения
|
||||
* @param message сообщение для логирования
|
||||
*/
|
||||
public void warn(String message) {
|
||||
this.log(LogLevel.WARN, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование сообщения об ошибке
|
||||
* @param message сообщение для логирования
|
||||
*/
|
||||
public void error(String message) {
|
||||
this.log(LogLevel.ERROR, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование отладочного сообщения
|
||||
* @param message сообщение для логирования
|
||||
*/
|
||||
public void debug(String message) {
|
||||
this.log(LogLevel.DEBUG, message);
|
||||
}
|
||||
|
||||
private String getColorForLogLevel(LogLevel logLevel) {
|
||||
return switch (logLevel) {
|
||||
case INFO -> Color.BLUE;
|
||||
case WARN -> Color.YELLOW;
|
||||
case ERROR -> Color.RED;
|
||||
case DEBUG -> Color.PURPLE;
|
||||
};
|
||||
}
|
||||
}
|
||||
13
src/main/java/im/rosetta/logger/enums/Color.java
Normal file
13
src/main/java/im/rosetta/logger/enums/Color.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package im.rosetta.logger.enums;
|
||||
|
||||
public final class Color {
|
||||
public static final String RESET = "\u001B[0m";
|
||||
public static final String BLACK = "\u001B[30m";
|
||||
public static final String RED = "\u001B[31m";
|
||||
public static final String GREEN = "\u001B[32m";
|
||||
public static final String YELLOW = "\u001B[33m";
|
||||
public static final String BLUE = "\u001B[34m";
|
||||
public static final String PURPLE = "\u001B[35m";
|
||||
public static final String CYAN = "\u001B[36m";
|
||||
public static final String WHITE = "\u001B[37m";
|
||||
}
|
||||
12
src/main/java/im/rosetta/logger/enums/LogLevel.java
Normal file
12
src/main/java/im/rosetta/logger/enums/LogLevel.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package im.rosetta.logger.enums;
|
||||
|
||||
public enum LogLevel {
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
DEBUG;
|
||||
|
||||
public boolean allows(LogLevel other) {
|
||||
return this.ordinal() <= other.ordinal();
|
||||
}
|
||||
}
|
||||
156
src/main/java/im/rosetta/packet/Packet0Handshake.java
Normal file
156
src/main/java/im/rosetta/packet/Packet0Handshake.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.runtime.HandshakeStage;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.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 String deviceId;
|
||||
private String deviceName;
|
||||
private String deviceOs;
|
||||
|
||||
/**
|
||||
* Стадия рукопожатия
|
||||
* 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.deviceId);
|
||||
stream.writeString(this.deviceName);
|
||||
stream.writeString(this.deviceOs);
|
||||
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.deviceId = deviceId;
|
||||
this.deviceName = deviceName;
|
||||
this.deviceOs = deviceOs;
|
||||
this.handshakeStage = HandshakeStage.fromCode(
|
||||
stream.readInt8()
|
||||
);
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public int getHeartbeatInterval() {
|
||||
return heartbeatInterval;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public String getDeviceOs() {
|
||||
return deviceOs;
|
||||
}
|
||||
|
||||
public HandshakeStage getHandshakeStage() {
|
||||
return handshakeStage;
|
||||
}
|
||||
|
||||
public void setHandshakeStage(HandshakeStage handshakeStage) {
|
||||
this.handshakeStage = handshakeStage;
|
||||
}
|
||||
|
||||
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) {
|
||||
this.heartbeatInterval = heartbeatInterval;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
}
|
||||
14
src/main/java/im/rosetta/packet/Packet10RequestUpdate.java
Normal file
14
src/main/java/im/rosetta/packet/Packet10RequestUpdate.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.base.PacketBaseServer;
|
||||
|
||||
/**
|
||||
* Получает сервер обновления
|
||||
*/
|
||||
public class Packet10RequestUpdate extends PacketBaseServer {
|
||||
/**
|
||||
* Пустой пакет, так как он наследник PacketBaseServer
|
||||
* который всегда имеет одну структуру.
|
||||
* Смотреть PacketBaseServer для реализации
|
||||
*/
|
||||
}
|
||||
29
src/main/java/im/rosetta/packet/Packet11Typeing.java
Normal file
29
src/main/java/im/rosetta/packet/Packet11Typeing.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.base.PacketBaseDialog;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
|
||||
/**
|
||||
* Пакет отвечающий за индикацию печати в диалогах
|
||||
*/
|
||||
public class Packet11Typeing extends PacketBaseDialog {
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.privateKey = stream.readString();
|
||||
this.fromPublicKey = stream.readString();
|
||||
this.toPublicKey = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.privateKey);
|
||||
stream.writeString(this.fromPublicKey);
|
||||
stream.writeString(this.toPublicKey);
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.base.PacketBaseServer;
|
||||
|
||||
/**
|
||||
* Пакет отправляется клиентом для запроса транспортного сервера, строка в этот момент клиентом
|
||||
* не заполняется, а уже обратно сервер заполняет строку и записывает туда транспортный сервер
|
||||
* чтобы клиент мог отправлять вложения на него
|
||||
*/
|
||||
public class Packet15RequestTransport extends PacketBaseServer {
|
||||
/**
|
||||
* Пустой пакет, так как он наследник PacketBaseServer
|
||||
* который всегда имеет одну структуру.
|
||||
* Смотреть PacketBaseServer для реализации
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.runtime.NetworkNotificationAction;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Packet16PushNotification бросается клиентом для подписки на пуш уведомления
|
||||
*/
|
||||
public class Packet16PushNotification extends Packet {
|
||||
|
||||
private String notificationToken;
|
||||
private NetworkNotificationAction action;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.notificationToken = stream.readString();
|
||||
this.action = NetworkNotificationAction.fromCode(stream.readInt8());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(notificationToken);
|
||||
stream.writeInt8(action.getCode());
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить токен пуш уведомлений, который нужно подписать или отписать в зависимости от action
|
||||
* @return токен пуш уведомлений
|
||||
*/
|
||||
public String getNotificationToken() {
|
||||
return notificationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить действие, которое нужно выполнить с токеном пуш уведомлений. SUBSCRIBE - подписать этот токен на пуш уведомления, UNSUBSCRIBE - отписать этот токен от пуш уведомлений
|
||||
* @return действие, которое нужно выполнить с токеном пуш уведомлений
|
||||
*/
|
||||
public NetworkNotificationAction getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает токен пуш уведомлений, который нужно подписать или отписать в зависимости от action
|
||||
* @param notificationToken токен пуш уведомлений
|
||||
*/
|
||||
public void setNotificationToken(String notificationToken) {
|
||||
this.notificationToken = notificationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает действие, которое нужно выполнить с токеном пуш уведомлений. SUBSCRIBE - подписать этот токен на пуш уведомления, UNSUBSCRIBE - отписать этот токен от пуш уведомлений
|
||||
* @param action действие, которое нужно выполнить с токеном пуш уведомлений
|
||||
*/
|
||||
public void setAction(NetworkNotificationAction action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
}
|
||||
39
src/main/java/im/rosetta/packet/Packet17GroupCreate.java
Normal file
39
src/main/java/im/rosetta/packet/Packet17GroupCreate.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class Packet17GroupCreate extends Packet {
|
||||
|
||||
private String groupId;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.groupId = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.groupId);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить id группы, которую нужно создать
|
||||
* @return id группы, которую нужно создать
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить id группы, которую нужно создать
|
||||
* @param groupId id группы, которую нужно создать
|
||||
*/
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
}
|
||||
67
src/main/java/im/rosetta/packet/Packet18GroupInfo.java
Normal file
67
src/main/java/im/rosetta/packet/Packet18GroupInfo.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class Packet18GroupInfo extends Packet {
|
||||
|
||||
private String groupId;
|
||||
private List<String> membersPKs;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.groupId = stream.readString();
|
||||
int membersCount = stream.readInt16();
|
||||
this.membersPKs = new java.util.ArrayList<>();
|
||||
for(int i = 0; i < membersCount; i++) {
|
||||
this.membersPKs.add(stream.readString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.groupId);
|
||||
stream.writeInt16(this.membersPKs.size());
|
||||
for(String memberPK : this.membersPKs) {
|
||||
stream.writeString(memberPK);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить id группы
|
||||
* @return id группы
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить id группы
|
||||
* @param groupId id группы
|
||||
*/
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить публичные ключи участников группы
|
||||
* @return список публичных ключей участников группы
|
||||
*/
|
||||
public List<String> getMembersPKs() {
|
||||
return this.membersPKs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить публичные ключи участников группы
|
||||
* @param membersPKs список публичных ключей участников группы
|
||||
*/
|
||||
public void setMembersPKs(List<String> membersPKs) {
|
||||
this.membersPKs = membersPKs;
|
||||
}
|
||||
|
||||
}
|
||||
82
src/main/java/im/rosetta/packet/Packet19GroupInviteInfo.java
Normal file
82
src/main/java/im/rosetta/packet/Packet19GroupInviteInfo.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.runtime.NetworkGroupStatus;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Пакет который бросается клиентом для определения статуса приглашения в группу
|
||||
*/
|
||||
public class Packet19GroupInviteInfo extends Packet {
|
||||
|
||||
private String groupId;
|
||||
private int membersCount;
|
||||
private NetworkGroupStatus status;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.groupId = stream.readString();
|
||||
this.membersCount = stream.readInt16();
|
||||
this.status = NetworkGroupStatus.fromCode(stream.readInt8());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.groupId);
|
||||
stream.writeInt16(this.membersCount);
|
||||
stream.writeInt8(this.status.getCode());
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить id группы
|
||||
* @return id группы
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить id группы
|
||||
* @param groupId id группы
|
||||
*/
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить количество участников в группе
|
||||
* @return количество участников в группе
|
||||
*/
|
||||
public int getMembersCount() {
|
||||
return this.membersCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить количество участников в группе
|
||||
* @param membersCount количество участников в группе
|
||||
*/
|
||||
public void setMembersCount(int membersCount) {
|
||||
this.membersCount = membersCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статус приглашения в группу
|
||||
* @return статус приглашения в группу
|
||||
*/
|
||||
public NetworkGroupStatus getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить статус приглашения в группу
|
||||
* @param status статус приглашения в группу
|
||||
*/
|
||||
public void setStatus(NetworkGroupStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
}
|
||||
84
src/main/java/im/rosetta/packet/Packet1UserInfo.java
Normal file
84
src/main/java/im/rosetta/packet/Packet1UserInfo.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class Packet1UserInfo extends Packet {
|
||||
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
private String privateKey;
|
||||
|
||||
private String username;
|
||||
private String title;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.username = stream.readString();
|
||||
this.title = stream.readString();
|
||||
this.privateKey = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream steram = new Stream();
|
||||
steram.writeInt16(this.packetId);
|
||||
steram.writeString(this.username);
|
||||
steram.writeString(this.title);
|
||||
steram.writeString(this.privateKey);
|
||||
return steram;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получает приватный ключ пользователя
|
||||
* @return приватный ключ
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
public String getPrivateKey() {
|
||||
return this.privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает приватный ключ пользователя
|
||||
* @param privateKey приватный ключ
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает имя пользователя
|
||||
* @return имя пользователя
|
||||
*/
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает заголовок (титул) пользователя
|
||||
* @return заголовок пользователя
|
||||
*/
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает имя пользователя
|
||||
* @param username имя пользователя
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает заголовок (титул) пользователя
|
||||
* @param title заголовок пользователя
|
||||
*/
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
65
src/main/java/im/rosetta/packet/Packet20GroupJoin.java
Normal file
65
src/main/java/im/rosetta/packet/Packet20GroupJoin.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.runtime.NetworkGroupStatus;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Вызывается клиентом для вступления в группу.
|
||||
* Сервер модифицирует этот пакет, устанавливая статус группы, и отправляет его обратно
|
||||
* клиенту
|
||||
*/
|
||||
public class Packet20GroupJoin extends Packet {
|
||||
|
||||
private String groupId;
|
||||
private NetworkGroupStatus status;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.groupId = stream.readString();
|
||||
this.status = NetworkGroupStatus.fromCode(stream.readInt8());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.groupId);
|
||||
stream.writeInt8(this.status.getCode());
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить id группы
|
||||
* @return id группы
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить id группы
|
||||
* @param groupId id группы
|
||||
*/
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статус группы
|
||||
* @return статус группы
|
||||
*/
|
||||
public NetworkGroupStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить статус группы
|
||||
* @param status статус группы
|
||||
*/
|
||||
public void setStatus(NetworkGroupStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
}
|
||||
42
src/main/java/im/rosetta/packet/Packet21GroupLeave.java
Normal file
42
src/main/java/im/rosetta/packet/Packet21GroupLeave.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Вызывается клиентом для выхода из группы. Содержит id группы, которую нужно покинуть
|
||||
*/
|
||||
public class Packet21GroupLeave extends Packet {
|
||||
|
||||
private String groupId;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.groupId = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.groupId);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить id группы, которую нужно покинуть
|
||||
* @return id группы, которую нужно покинуть
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить id группы, которую нужно покинуть
|
||||
* @param groupId id группы, которую нужно покинуть
|
||||
*/
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
}
|
||||
58
src/main/java/im/rosetta/packet/Packet22GroupBan.java
Normal file
58
src/main/java/im/rosetta/packet/Packet22GroupBan.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class Packet22GroupBan extends Packet {
|
||||
|
||||
private String groupId;
|
||||
private String publicKey;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.groupId = stream.readString();
|
||||
this.publicKey = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.groupId);
|
||||
stream.writeString(this.publicKey);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить id группы, в которой нужно забанить пользователя
|
||||
* @return id группы
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить id группы, в которой нужно забанить пользователя
|
||||
* @param groupId id группы
|
||||
*/
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить публичный ключ пользователя, которого нужно забанить в группе
|
||||
* @return публичный ключ пользователя
|
||||
*/
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить публичный ключ пользователя, которого нужно забанить в группе
|
||||
* @param publicKey публичный ключ
|
||||
*/
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
}
|
||||
62
src/main/java/im/rosetta/packet/Packet23DeviceList.java
Normal file
62
src/main/java/im/rosetta/packet/Packet23DeviceList.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.packet.runtime.DeviceSolution;
|
||||
import im.rosetta.packet.runtime.NetworkDevice;
|
||||
import im.rosetta.packet.runtime.NetworkStatus;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Пакет, который содержит список устройств, с которых был произведен вход в систему.
|
||||
* Этот пакет может быть отправлен сервером в ответ на запрос клиента о получении списка устройств,
|
||||
* или может быть отправлен сервером при обнаружении нового входа в систему с нового устройства, чтобы уведомить клиента о новом устройстве.
|
||||
*/
|
||||
public class Packet23DeviceList extends Packet {
|
||||
|
||||
private List<NetworkDevice> devices;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
int deviceCount = stream.readInt16();
|
||||
this.devices = new java.util.ArrayList<>();
|
||||
for(int i = 0; i < deviceCount; i++) {
|
||||
NetworkDevice netDevice = new NetworkDevice();
|
||||
netDevice.setDeviceId(stream.readString());
|
||||
netDevice.setDeviceName(stream.readString());
|
||||
netDevice.setDeviceOs(stream.readString());
|
||||
/**
|
||||
* TODO: Использовать boolean для обозначения статуса сети, а не int8.
|
||||
*/
|
||||
netDevice.setNetworkStatus(NetworkStatus.fromCode(stream.readInt8()));
|
||||
netDevice.setDeviceSolution(DeviceSolution.fromCode(stream.readInt8()));
|
||||
this.devices.add(netDevice);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeInt16(this.devices.size());
|
||||
for(NetworkDevice device : this.devices) {
|
||||
stream.writeString(device.getDeviceId());
|
||||
stream.writeString(device.getDeviceName());
|
||||
stream.writeString(device.getDeviceOs());
|
||||
stream.writeInt8(device.getNetworkStatus().getCode());
|
||||
stream.writeInt8(device.getDeviceSolution().getCode());
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
public List<NetworkDevice> getDevices() {
|
||||
return devices;
|
||||
}
|
||||
|
||||
public void setDevices(List<NetworkDevice> devices) {
|
||||
this.devices = devices;
|
||||
}
|
||||
|
||||
}
|
||||
48
src/main/java/im/rosetta/packet/Packet24DeviceResolve.java
Normal file
48
src/main/java/im/rosetta/packet/Packet24DeviceResolve.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.runtime.DeviceSolution;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Пакет для решения по запросу на добавление устройства
|
||||
* Принимается от клиента, который получил запрос на добавление устройства, и отправляется серверу для обработки решения
|
||||
*/
|
||||
public class Packet24DeviceResolve extends Packet {
|
||||
|
||||
private String deviceId;
|
||||
private DeviceSolution solution;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.deviceId = stream.readString();
|
||||
this.solution = DeviceSolution.fromCode(stream.readInt8());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.deviceId);
|
||||
stream.writeInt8(this.solution.getCode());
|
||||
return stream;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public DeviceSolution getSolution() {
|
||||
return solution;
|
||||
}
|
||||
|
||||
public void setSolution(DeviceSolution solution) {
|
||||
this.solution = solution;
|
||||
}
|
||||
|
||||
}
|
||||
42
src/main/java/im/rosetta/packet/Packet2Result.java
Normal file
42
src/main/java/im/rosetta/packet/Packet2Result.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.runtime.ResultCode;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class Packet2Result extends Packet {
|
||||
|
||||
private ResultCode resultCode;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.resultCode = ResultCode.fromCode(stream.readInt16());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeInt16(this.resultCode.getCode());
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает код результата операции
|
||||
* @return код результата
|
||||
*/
|
||||
public ResultCode getResultCode() {
|
||||
return this.resultCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает код результата операции
|
||||
* @param resultCode код результата
|
||||
*/
|
||||
public void setResultCode(ResultCode resultCode) {
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
98
src/main/java/im/rosetta/packet/Packet3Search.java
Normal file
98
src/main/java/im/rosetta/packet/Packet3Search.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.packet.runtime.SearchInfo;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class Packet3Search extends Packet {
|
||||
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
private String privateKey;
|
||||
private String search;
|
||||
private List<SearchInfo> searchInfo;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.privateKey = stream.readString();
|
||||
this.search = stream.readString();
|
||||
int searchInfoCount = stream.readInt16();
|
||||
this.searchInfo = new ArrayList<>();
|
||||
for (int i = 0; i < searchInfoCount; i++) {
|
||||
SearchInfo info = new SearchInfo();
|
||||
info.readFromStream(stream);
|
||||
this.searchInfo.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.privateKey);
|
||||
stream.writeString(this.search);
|
||||
stream.writeInt16(this.searchInfo.size());
|
||||
for (SearchInfo info : this.searchInfo) {
|
||||
info.writeToStream(stream);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает приватный ключ пользователя
|
||||
* @return приватный ключ
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
public String getPrivateKey() {
|
||||
return this.privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает приватный ключ пользователя
|
||||
* @param privateKey приватный ключ
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает строку поиска
|
||||
* @return строка поиска
|
||||
*/
|
||||
public String getSearch() {
|
||||
return this.search;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает строку поиска
|
||||
* @param search строка поиска
|
||||
*/
|
||||
public void setSearch(String search) {
|
||||
this.search = search;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает результаты поиска
|
||||
* @return список результатов
|
||||
*/
|
||||
public List<SearchInfo> getSearchInfos() {
|
||||
return this.searchInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает результаты поиска
|
||||
* @param searchInfo
|
||||
*/
|
||||
public void setSearchInfos(List<SearchInfo> searchInfos) {
|
||||
this.searchInfo = searchInfos;
|
||||
}
|
||||
|
||||
}
|
||||
73
src/main/java/im/rosetta/packet/Packet4OnlineSubscribe.java
Normal file
73
src/main/java/im/rosetta/packet/Packet4OnlineSubscribe.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class Packet4OnlineSubscribe extends Packet {
|
||||
|
||||
private String privateKey;
|
||||
private List<String> publicKeys;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.privateKey = stream.readString();
|
||||
int publicKeysCount = stream.readInt16();
|
||||
this.publicKeys = new java.util.ArrayList<>();
|
||||
for (int i = 0; i < publicKeysCount; i++) {
|
||||
this.publicKeys.add(stream.readString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.privateKey);
|
||||
stream.writeInt16(this.publicKeys.size());
|
||||
for (String publicKey : this.publicKeys) {
|
||||
stream.writeString(publicKey);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает приватный ключ пользователя
|
||||
* @return приватный ключ
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
public String getPrivateKey() {
|
||||
return this.privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает приватный ключ пользователя
|
||||
* @param privateKey приватный ключ
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает список публичных ключей для подписки на онлайн статус
|
||||
* @return список публичных ключей
|
||||
*/
|
||||
public List<String> getPublicKeys() {
|
||||
return this.publicKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает список публичных ключей для подписки на онлайн статус
|
||||
* @param publicKeys список публичных ключей
|
||||
*/
|
||||
public void setPublicKeys(List<String> publicKeys) {
|
||||
this.publicKeys = publicKeys;
|
||||
}
|
||||
|
||||
}
|
||||
47
src/main/java/im/rosetta/packet/Packet5OnlineState.java
Normal file
47
src/main/java/im/rosetta/packet/Packet5OnlineState.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.packet.runtime.NetworkStatus;
|
||||
import im.rosetta.packet.runtime.PKNetworkStatus;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class Packet5OnlineState extends Packet {
|
||||
|
||||
private List<PKNetworkStatus> pkNetworkStatuses;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
int count = stream.readInt8();
|
||||
this.pkNetworkStatuses = new java.util.ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String publicKey = stream.readString();
|
||||
boolean online = stream.readBoolean();
|
||||
PKNetworkStatus status = new PKNetworkStatus(publicKey, NetworkStatus.fromBoolean(online));
|
||||
this.pkNetworkStatuses.add(status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeInt8(this.pkNetworkStatuses.size());
|
||||
for (PKNetworkStatus status : this.pkNetworkStatuses) {
|
||||
stream.writeString(status.getPublicKey());
|
||||
stream.writeBoolean(status.getNetworkStatus().toBoolean());
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
public List<PKNetworkStatus> getPkNetworkStatuses() {
|
||||
return pkNetworkStatuses;
|
||||
}
|
||||
|
||||
public void setPkNetworkStatuses(List<PKNetworkStatus> pkNetworkStatuses) {
|
||||
this.pkNetworkStatuses = pkNetworkStatuses;
|
||||
}
|
||||
|
||||
}
|
||||
193
src/main/java/im/rosetta/packet/Packet6Message.java
Normal file
193
src/main/java/im/rosetta/packet/Packet6Message.java
Normal file
@@ -0,0 +1,193 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import im.rosetta.packet.base.PacketBaseDialog;
|
||||
import im.rosetta.packet.runtime.Attachment;
|
||||
import im.rosetta.packet.runtime.AttachmentType;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
|
||||
/**
|
||||
* Пакет для отправки сообщений между пользователями. Содержит зашифрованное текстовое содержимое и массив вложений с
|
||||
* их метаданными. Временная метка используется для сортировки сообщений и отображения времени отправки. Идентификатор
|
||||
* сообщения нужен для редактирования и удаления сообщений. Ключ chacha используется для шифрования текста сообщения,
|
||||
* а aesChachaKey нужен для последующей синхронизации своих же сообщений на других устройствах.
|
||||
* Сам ключ ChaCha20 во избежания обемена ключами зашифрован ECC.
|
||||
*/
|
||||
public class Packet6Message extends PacketBaseDialog {
|
||||
/**
|
||||
* Текстовое содержимое сообщения, зашифровано ChaCha20, зашифровано ECC
|
||||
*/
|
||||
private String content;
|
||||
/**
|
||||
* Ключ chacha для шифрования сообщения, зашифрован ECC
|
||||
*/
|
||||
private String chachaKey;
|
||||
/**
|
||||
* Временная метка сообщения в миллисекундах
|
||||
*/
|
||||
private long timestamp;
|
||||
/**
|
||||
* Идентификатор сообщения, нужен для редактирования и удаления сообщений
|
||||
*/
|
||||
private String messageId;
|
||||
/**
|
||||
* Массив вложений в сообщении
|
||||
*/
|
||||
private List<Attachment> attachments;
|
||||
/**
|
||||
* Закодированный с помощью AES ключ chacha, нужен
|
||||
* для последующей синхронизации своих же сообщений
|
||||
*/
|
||||
private String aesChachaKey;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.fromPublicKey = stream.readString();
|
||||
this.toPublicKey = stream.readString();
|
||||
this.content = stream.readString();
|
||||
this.chachaKey = stream.readString();
|
||||
this.timestamp = stream.readInt64();
|
||||
this.privateKey = stream.readString();
|
||||
this.messageId = stream.readString();
|
||||
int attachmentsCount = stream.readInt8();
|
||||
this.attachments = new java.util.ArrayList<>();
|
||||
for (int i = 0; i < attachmentsCount; i++) {
|
||||
String id = stream.readString();
|
||||
String preview = stream.readString();
|
||||
String blob = stream.readString();
|
||||
AttachmentType type = AttachmentType.fromCode(stream.readInt8());
|
||||
this.attachments.add(new Attachment(id, blob, type, preview));
|
||||
}
|
||||
this.aesChachaKey = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.fromPublicKey);
|
||||
stream.writeString(this.toPublicKey);
|
||||
stream.writeString(this.content);
|
||||
stream.writeString(this.chachaKey);
|
||||
stream.writeInt64(this.timestamp);
|
||||
stream.writeString(this.privateKey);
|
||||
stream.writeString(this.messageId);
|
||||
stream.writeInt8(this.attachments.size());
|
||||
for (Attachment attachment : this.attachments) {
|
||||
stream.writeString(attachment.getId());
|
||||
stream.writeString(attachment.getPreview());
|
||||
stream.writeString(attachment.getBlob());
|
||||
stream.writeInt8((byte) attachment.getType().getCode());
|
||||
}
|
||||
stream.writeString(this.aesChachaKey);
|
||||
return stream;
|
||||
}
|
||||
/**
|
||||
* Получить текстовое содержимое сообщения, зашифровано ChaCha20, зашифровано ECC
|
||||
* @return текстовое содержимое сообщения
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
/**
|
||||
* Получить ключ chacha для шифрования сообщения, зашифрован ECC
|
||||
* @return ключ chacha
|
||||
*/
|
||||
public String getChachaKey() {
|
||||
return chachaKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить временную метку сообщения в миллисекундах
|
||||
* @return временная метка сообщения в мсиллисекундах
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить идентификатор сообщения
|
||||
* @return идентификатор сообщения
|
||||
*/
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить массив вложений в сообщении
|
||||
* @return массив вложений в сообщении
|
||||
*/
|
||||
public List<Attachment> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить закодированный с помощью AES ключ chacha
|
||||
* @return ключ chacha
|
||||
*/
|
||||
public String getAesChachaKey() {
|
||||
return aesChachaKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает текстовое содержимое сообщения
|
||||
* @param content текстовое содержимое сообщения
|
||||
*/
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает ключ chacha для шифрования сообщения
|
||||
* @param chachaKey
|
||||
*/
|
||||
public void setChachaKey(String chachaKey) {
|
||||
this.chachaKey = chachaKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает временную метку сообщения в миллисекундах
|
||||
* @param timestamp временная метка сообщения в миллисекундах
|
||||
*/
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает приватный ключ пользователя
|
||||
* @param privateKey приватный ключ
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает идентификатор сообщения
|
||||
* @param messageId идентификатор сообщения
|
||||
*/
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает массив вложений в сообщении
|
||||
* @param attachments массив вложений в сообщении
|
||||
*/
|
||||
public void setAttachments(List<Attachment> attachments) {
|
||||
this.attachments = attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает закодированный с помощью AES ключ chacha
|
||||
* @param aesChachaKey ключ chacha
|
||||
*/
|
||||
public void setAesChachaKey(String aesChachaKey) {
|
||||
this.aesChachaKey = aesChachaKey;
|
||||
}
|
||||
|
||||
}
|
||||
28
src/main/java/im/rosetta/packet/Packet7Read.java
Normal file
28
src/main/java/im/rosetta/packet/Packet7Read.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import im.rosetta.packet.base.PacketBaseDialog;
|
||||
import io.orprotocol.Stream;
|
||||
|
||||
/**
|
||||
* Пакет для отметки сообщения как прочитанного
|
||||
*/
|
||||
public class Packet7Read extends PacketBaseDialog {
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.privateKey = stream.readString();
|
||||
this.fromPublicKey = stream.readString();
|
||||
this.toPublicKey = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.privateKey);
|
||||
stream.writeString(this.fromPublicKey);
|
||||
stream.writeString(this.toPublicKey);
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
61
src/main/java/im/rosetta/packet/Packet8Delivery.java
Normal file
61
src/main/java/im/rosetta/packet/Packet8Delivery.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Пакет обозначающий доставку сообщения получателю. Отправляется после успешной доставки сообщения получателю
|
||||
*/
|
||||
public class Packet8Delivery extends Packet {
|
||||
|
||||
private String messageId;
|
||||
private String toPublicKey;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.toPublicKey = stream.readString();
|
||||
this.messageId = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.toPublicKey);
|
||||
stream.writeString(this.messageId);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить идентификатор доставленного сообщения
|
||||
* @return идентификатор доставленного сообщения
|
||||
*/
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить публичный ключ получателя доставленного сообщения
|
||||
* @return публичный ключ получателя доставленного сообщения
|
||||
*/
|
||||
public String getToPublicKey() {
|
||||
return toPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить идентификатор доставленного сообщения
|
||||
* @param messageId идентификатор доставленного сообщения
|
||||
*/
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить публичный ключ получателя доставленного сообщения
|
||||
* @param toPublicKey публичный ключ получателя доставленного сообщения
|
||||
*/
|
||||
public void setToPublicKey(String toPublicKey) {
|
||||
this.toPublicKey = toPublicKey;
|
||||
}
|
||||
|
||||
}
|
||||
72
src/main/java/im/rosetta/packet/Packet9DeviceNew.java
Normal file
72
src/main/java/im/rosetta/packet/Packet9DeviceNew.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package im.rosetta.packet;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Пакет для уведомления о новом устройстве, авторизовавшемся с учетной записью пользователя
|
||||
* Этот пакет может быть отправлен сервером всем авторизованным устройствам пользователя,
|
||||
* чтобы уведомить их о том, что с учетной записью было авторизовано новое устройство, и предоставить информацию об этом устройстве (например, IP-адрес, тип устройства, операционная система и т.д.)
|
||||
* Клиенты могут использовать эту информацию для отображения уведомления пользователю,
|
||||
* а также для обеспечения безопасности учетной записи (например, если пользователь не узнает устройство, он может предпринять меры
|
||||
* для защиты своей учетной записи, например, заблокировать вход для нового устройства)
|
||||
*/
|
||||
public class Packet9DeviceNew extends Packet {
|
||||
|
||||
private String ipAddress;
|
||||
private String deviceId;
|
||||
private String deviceName;
|
||||
private String deviceOs;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.ipAddress = stream.readString();
|
||||
this.deviceId = stream.readString();
|
||||
this.deviceName = stream.readString();
|
||||
this.deviceOs = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.ipAddress);
|
||||
stream.writeString(this.deviceId);
|
||||
stream.writeString(this.deviceName);
|
||||
stream.writeString(this.deviceOs);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public void setDeviceName(String deviceName) {
|
||||
this.deviceName = deviceName;
|
||||
}
|
||||
|
||||
public String getDeviceOs() {
|
||||
return deviceOs;
|
||||
}
|
||||
|
||||
public void setDeviceOs(String deviceOs) {
|
||||
this.deviceOs = deviceOs;
|
||||
}
|
||||
|
||||
}
|
||||
96
src/main/java/im/rosetta/packet/base/PacketBaseDialog.java
Normal file
96
src/main/java/im/rosetta/packet/base/PacketBaseDialog.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package im.rosetta.packet.base;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
/**
|
||||
* Базовый пакет для диалогов между пользователями
|
||||
*
|
||||
* ВОПРОС: Почему мы должны отправлять fromPublicKey с клиента, если сервер
|
||||
* может получить fromPublicKey из хэндшейка?
|
||||
* ОТВЕТ: Клиенты (оппоненты) должны понимать, от кого им приходит например пакет сообщения
|
||||
* или печати с сервера (from), чтобы производить с отправителем какие-либо действия (например показать
|
||||
* имя печатающего или отрендерить аватарку отправтеля сообщения), если бы поле fromPublicKey заполнял
|
||||
* сервер - это бы выглядело не логично. Это не влияет на безопасность, так как каждый Exectuor
|
||||
* верифицирует поле fromPublicKey сравнивая его с публичным ключом фактического отправителя.
|
||||
*/
|
||||
public class PacketBaseDialog extends Packet {
|
||||
|
||||
/**
|
||||
* Публичный ключ отправителя
|
||||
*/
|
||||
public String fromPublicKey;
|
||||
/**
|
||||
* Публичный ключ получателя, может начинаться с #group: для групповых сообщений
|
||||
*/
|
||||
public String toPublicKey;
|
||||
/**
|
||||
* Приватный ключ отправителя
|
||||
*/
|
||||
public String privateKey;
|
||||
|
||||
/**
|
||||
* Заглушка
|
||||
*/
|
||||
@Override
|
||||
public void read(Stream stream) {}
|
||||
/**
|
||||
* Заглушка
|
||||
*/
|
||||
@Override
|
||||
public Stream write() {return null;}
|
||||
|
||||
/**
|
||||
* Получить публичный ключ отправителя
|
||||
* @return публичный ключ отправителя
|
||||
*/
|
||||
public String getFromPublicKey() {
|
||||
return fromPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить публичный ключ получателя
|
||||
* @return публичный ключ получателя
|
||||
*/
|
||||
public String getToPublicKey() {
|
||||
return toPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает приватный ключ пользователя
|
||||
* @return приватный ключ
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
public String getPrivateKey() {
|
||||
return this.privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает приватный ключ пользователя
|
||||
* @param privateKey приватный ключ
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает публичный ключ отправителя
|
||||
* @param fromPublicKey публичный ключ отправителя
|
||||
*/
|
||||
public void setFromPublicKey(String fromPublicKey) {
|
||||
this.fromPublicKey = fromPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает публичный ключ получателя
|
||||
* @param toPublicKey публичный ключ получателя
|
||||
*/
|
||||
public void setToPublicKey(String toPublicKey) {
|
||||
this.toPublicKey = toPublicKey;
|
||||
}
|
||||
}
|
||||
37
src/main/java/im/rosetta/packet/base/PacketBaseServer.java
Normal file
37
src/main/java/im/rosetta/packet/base/PacketBaseServer.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package im.rosetta.packet.base;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class PacketBaseServer extends Packet {
|
||||
private String server;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.server = stream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeString(this.server);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить сервер
|
||||
* @return сервер
|
||||
*/
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить сервер
|
||||
* @param server сервер
|
||||
*/
|
||||
public void setServer(String server) {
|
||||
this.server = server;
|
||||
}
|
||||
}
|
||||
52
src/main/java/im/rosetta/packet/runtime/Attachment.java
Normal file
52
src/main/java/im/rosetta/packet/runtime/Attachment.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
/**
|
||||
* Вложение в сообщении
|
||||
*/
|
||||
public class Attachment {
|
||||
|
||||
private String id;
|
||||
private String blob;
|
||||
private AttachmentType type;
|
||||
private String preview;
|
||||
|
||||
public Attachment(String id, String blob, AttachmentType type, String preview) {
|
||||
this.id = id;
|
||||
this.blob = blob;
|
||||
this.type = type;
|
||||
this.preview = preview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить идентификатор вложения
|
||||
* @return
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить данные вложения в виде строки
|
||||
* @return
|
||||
*/
|
||||
public String getBlob() {
|
||||
return blob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить тип вложения
|
||||
* @return
|
||||
*/
|
||||
public AttachmentType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить превью вложения (например, для изображений)
|
||||
* @return
|
||||
*/
|
||||
public String getPreview() {
|
||||
return preview;
|
||||
}
|
||||
|
||||
}
|
||||
31
src/main/java/im/rosetta/packet/runtime/AttachmentType.java
Normal file
31
src/main/java/im/rosetta/packet/runtime/AttachmentType.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
/**
|
||||
* Тип вложения в сообщении
|
||||
*/
|
||||
public enum AttachmentType {
|
||||
IMAGE(0),
|
||||
MESSAGES(1),
|
||||
FILE(2),
|
||||
AVATAR(3);
|
||||
|
||||
|
||||
private final int code;
|
||||
|
||||
AttachmentType(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static AttachmentType fromCode(int code) {
|
||||
for (AttachmentType type : AttachmentType.values()) {
|
||||
if (type.getCode() == code) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid AttachmentType code: " + code);
|
||||
}
|
||||
}
|
||||
33
src/main/java/im/rosetta/packet/runtime/DeviceSolution.java
Normal file
33
src/main/java/im/rosetta/packet/runtime/DeviceSolution.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
/**
|
||||
* Решение по запросу на добавление устройства
|
||||
*/
|
||||
public enum DeviceSolution {
|
||||
/**
|
||||
* Принять запрос на добавление устройства
|
||||
*/
|
||||
ACCEPT(0),
|
||||
/**
|
||||
* Отклонить запрос на добавление устройства
|
||||
*/
|
||||
DECLINE(1);
|
||||
|
||||
private int code;
|
||||
private DeviceSolution(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static DeviceSolution fromCode(int code) {
|
||||
for (DeviceSolution solution : DeviceSolution.values()) {
|
||||
if (solution.getCode() == code) {
|
||||
return solution;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown DeviceSolution value: " + code);
|
||||
}
|
||||
}
|
||||
36
src/main/java/im/rosetta/packet/runtime/HandshakeStage.java
Normal file
36
src/main/java/im/rosetta/packet/runtime/HandshakeStage.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
/**
|
||||
* Этапы хэндшейка между клиентом и сервером.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
68
src/main/java/im/rosetta/packet/runtime/NetworkDevice.java
Normal file
68
src/main/java/im/rosetta/packet/runtime/NetworkDevice.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
/**
|
||||
* Обозначает подключенное к аккаунту устройство, с которого
|
||||
* был произведен вход в систему.
|
||||
*/
|
||||
public class NetworkDevice {
|
||||
|
||||
private NetworkStatus networkStatus;
|
||||
private String deviceName;
|
||||
private String deviceOs;
|
||||
private String deviceId;
|
||||
private DeviceSolution deviceSolution;
|
||||
|
||||
public NetworkDevice() {
|
||||
}
|
||||
|
||||
public NetworkDevice(NetworkStatus networkStatus, String deviceName, String deviceOs, String deviceId,
|
||||
DeviceSolution deviceSolution) {
|
||||
this.networkStatus = networkStatus;
|
||||
this.deviceName = deviceName;
|
||||
this.deviceOs = deviceOs;
|
||||
this.deviceId = deviceId;
|
||||
this.deviceSolution = deviceSolution;
|
||||
}
|
||||
|
||||
|
||||
public NetworkStatus getNetworkStatus() {
|
||||
return networkStatus;
|
||||
}
|
||||
|
||||
public void setNetworkStatus(NetworkStatus networkStatus) {
|
||||
this.networkStatus = networkStatus;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public void setDeviceName(String deviceName) {
|
||||
this.deviceName = deviceName;
|
||||
}
|
||||
|
||||
public String getDeviceOs() {
|
||||
return deviceOs;
|
||||
}
|
||||
|
||||
public void setDeviceOs(String deviceOs) {
|
||||
this.deviceOs = deviceOs;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public DeviceSolution getDeviceSolution() {
|
||||
return deviceSolution;
|
||||
}
|
||||
|
||||
public void setDeviceSolution(DeviceSolution deviceSolution) {
|
||||
this.deviceSolution = deviceSolution;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
/**
|
||||
* Используется в Packet19GroupInviteInfo для указания статуса пользователя в группе.
|
||||
*/
|
||||
public enum NetworkGroupStatus {
|
||||
/**
|
||||
* Пользователь уже в группе
|
||||
*/
|
||||
JOINED(0),
|
||||
/**
|
||||
* Пользователь не может вступить в группу, так как она не существует или произошла ошибка
|
||||
*/
|
||||
INVALID(1),
|
||||
/**
|
||||
* Пользователь не вступил в группу, но может вступить, так как она существует и он не был из нее исключен
|
||||
*/
|
||||
NOT_JOINED(2),
|
||||
/**
|
||||
* Пользователь исключен из группы и не может вступить в нее, так как он был из нее исключен администратором
|
||||
*/
|
||||
BANNED(3);
|
||||
|
||||
private final int code;
|
||||
|
||||
NetworkGroupStatus(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static NetworkGroupStatus fromCode(int code) {
|
||||
for (NetworkGroupStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown NetworkGroupStatus code: " + code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
/**
|
||||
* Используется в Packet16PushNotification для указания действия, которое нужно выполнить с токеном пуш уведомлений.
|
||||
*/
|
||||
public enum NetworkNotificationAction {
|
||||
/**
|
||||
* Подписать этот токен на пуш уведомления. Если токен уже был подписан, то ничего не произойдет.
|
||||
*/
|
||||
SUBSCRIBE(0),
|
||||
/**
|
||||
* Отписать этот токен от пуш уведомлений. Если токен не был подписан, то ничего не произойдет.
|
||||
*/
|
||||
UNSUBSCRIBE(1);
|
||||
|
||||
private final int code;
|
||||
|
||||
NetworkNotificationAction(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static NetworkNotificationAction fromCode(int code) {
|
||||
for (NetworkNotificationAction action : values()) {
|
||||
if (action.code == code) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown NetworkNotificationAction code: " + code);
|
||||
}
|
||||
}
|
||||
39
src/main/java/im/rosetta/packet/runtime/NetworkStatus.java
Normal file
39
src/main/java/im/rosetta/packet/runtime/NetworkStatus.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
/**
|
||||
* Статус пользователя в сети
|
||||
*/
|
||||
public enum NetworkStatus {
|
||||
ONLINE(0),
|
||||
OFFLINE(1);
|
||||
|
||||
private final int code;
|
||||
|
||||
NetworkStatus(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static NetworkStatus fromCode(int code) {
|
||||
for (NetworkStatus status : NetworkStatus.values()) {
|
||||
if (status.getCode() == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid NetworkStatus code: " + code);
|
||||
}
|
||||
|
||||
public static NetworkStatus fromBoolean(boolean status) {
|
||||
if(status){
|
||||
return NetworkStatus.ONLINE;
|
||||
}
|
||||
return NetworkStatus.OFFLINE;
|
||||
}
|
||||
|
||||
public boolean toBoolean() {
|
||||
return this.code == 0;
|
||||
}
|
||||
}
|
||||
35
src/main/java/im/rosetta/packet/runtime/PKNetworkStatus.java
Normal file
35
src/main/java/im/rosetta/packet/runtime/PKNetworkStatus.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
/**
|
||||
* Сущность для обозначения статуса сети
|
||||
* пользователя по публичному ключу
|
||||
*/
|
||||
public class PKNetworkStatus {
|
||||
|
||||
public String publicKey;
|
||||
public NetworkStatus networkStatus;
|
||||
|
||||
public PKNetworkStatus() {}
|
||||
|
||||
public PKNetworkStatus(String publicKey, NetworkStatus networkStatus) {
|
||||
this.publicKey = publicKey;
|
||||
this.networkStatus = networkStatus;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public NetworkStatus getNetworkStatus() {
|
||||
return networkStatus;
|
||||
}
|
||||
|
||||
public void setNetworkStatus(NetworkStatus networkStatus) {
|
||||
this.networkStatus = networkStatus;
|
||||
}
|
||||
|
||||
}
|
||||
27
src/main/java/im/rosetta/packet/runtime/ResultCode.java
Normal file
27
src/main/java/im/rosetta/packet/runtime/ResultCode.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package im.rosetta.packet.runtime;
|
||||
|
||||
public enum ResultCode {
|
||||
SUCCESS(0),
|
||||
ERROR(1),
|
||||
INVALID(2),
|
||||
USERNAME_TAKEN(3);
|
||||
|
||||
private final int code;
|
||||
|
||||
ResultCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static ResultCode fromCode(int code) {
|
||||
for (ResultCode rc : ResultCode.values()) {
|
||||
if (rc.getCode() == code) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid ResultCode code: " + code);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user