From c1b4ca5db767187b00cde850f35400deef8f1d7a Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Mon, 2 Feb 2026 02:08:53 +0200 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=BD=D0=B8=D1=86=D0=B8=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF=D1=80=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=BA=D0=BE=D0=BB=D0=B0=20Rosetta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/rosetta/im/Boot.java | 42 ++++++++ src/main/java/com/rosetta/im/Main.java | 22 ++-- .../im/executors/Executor0Handshake.java | 9 +- .../rosetta/im/packet/Packet0Handshake.java | 53 ++++++++++ .../java/com/rosetta/im/protocol/Client.java | 97 +++++++++++++++++- .../com/rosetta/im/protocol/Failures.java | 55 ++++++++++ .../java/com/rosetta/im/protocol/Server.java | 87 +++++++++++++--- .../com/rosetta/im/protocol/Settings.java | 18 ++++ .../im/protocol/packet/PacketExecutor.java | 4 +- .../im/protocol/packet/PacketManager.java | 60 ++++++++++- target/classes/com/rosetta/im/Main.class | Bin 996 -> 1061 bytes .../im/executors/Executor0Handshake.class | Bin 710 -> 953 bytes .../rosetta/im/packet/Packet0Handshake.class | Bin 1832 -> 3038 bytes .../com/rosetta/im/protocol/Client.class | Bin 1783 -> 2798 bytes .../com/rosetta/im/protocol/Server.class | Bin 3828 -> 6559 bytes .../im/protocol/packet/PacketExecutor.class | Bin 275 -> 244 bytes .../im/protocol/packet/PacketManager.class | Bin 2462 -> 3364 bytes 17 files changed, 421 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/rosetta/im/Boot.java create mode 100644 src/main/java/com/rosetta/im/protocol/Failures.java create mode 100644 src/main/java/com/rosetta/im/protocol/Settings.java diff --git a/src/main/java/com/rosetta/im/Boot.java b/src/main/java/com/rosetta/im/Boot.java new file mode 100644 index 0000000..0549359 --- /dev/null +++ b/src/main/java/com/rosetta/im/Boot.java @@ -0,0 +1,42 @@ +package com.rosetta.im; + +import com.rosetta.im.executors.Executor0Handshake; +import com.rosetta.im.packet.Packet0Handshake; +import com.rosetta.im.protocol.packet.PacketManager; + +public class Boot { + + private PacketManager packetManager; + + public Boot(PacketManager packetManager) { + this.packetManager = packetManager; + } + + public PacketManager getPacketManager() { + return this.packetManager; + } + + /** + * Инициализация всех пакетов и их обработчиков + */ + public void bootstrap() { + this.registerAllPackets(); + this.registerAllExecutors(); + this.printBootMessage(); + } + + private void registerAllPackets() { + this.packetManager.registerPacket(0, Packet0Handshake.class); + } + + private void registerAllExecutors() { + this.packetManager.registerExecutor(0, Executor0Handshake.class); + } + + private void printBootMessage() { + System.out.println("Bootstrapping completed. All packets and executors are registered."); + System.out.println("Total packets registered: " + this.packetManager.totalPackets()); + System.out.println("Total executors registered: " + this.packetManager.totalExecutors()); + } + +} diff --git a/src/main/java/com/rosetta/im/Main.java b/src/main/java/com/rosetta/im/Main.java index f09e66a..20f20de 100644 --- a/src/main/java/com/rosetta/im/Main.java +++ b/src/main/java/com/rosetta/im/Main.java @@ -1,20 +1,28 @@ package com.rosetta.im; - -import com.rosetta.im.packet.Packet0Handshake; import com.rosetta.im.protocol.Server; +import com.rosetta.im.protocol.Settings; import com.rosetta.im.protocol.packet.PacketManager; public class Main { public static void main(String[] args) { PacketManager manager = new PacketManager(); - manager.registerPacket(0, Packet0Handshake.class); + /** + * Регистрация всех пакетов и их обработчиков + */ + Boot boot = new Boot(manager); + boot.bootstrap(); - Server server = new Server(8881, manager); + /** + * Загрузка настроек сервера + */ + Settings settings = new Settings(8881, 30); + + /** + * Запуск сервера на порту 8881 + */ + Server server = new Server(settings, manager); server.start(); - - - System.out.println("Rosetta server started..."); } } \ No newline at end of file diff --git a/src/main/java/com/rosetta/im/executors/Executor0Handshake.java b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java index 349fab1..eda8ba5 100644 --- a/src/main/java/com/rosetta/im/executors/Executor0Handshake.java +++ b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java @@ -1,13 +1,18 @@ package com.rosetta.im.executors; +import com.rosetta.im.packet.Packet0Handshake; +import com.rosetta.im.protocol.Client; import com.rosetta.im.protocol.packet.Packet; import com.rosetta.im.protocol.packet.PacketExecutor; public class Executor0Handshake implements PacketExecutor { @Override - public void onPacketReceived(Class packet) { - + public void onPacketReceived(Packet packet, Client client) { + Packet0Handshake handshake = (Packet0Handshake) packet; + String publicKey = handshake.getPublicKey(); + String privateKey = handshake.getPrivateKey(); + int b = 0; } } diff --git a/src/main/java/com/rosetta/im/packet/Packet0Handshake.java b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java index 6389fce..f21ec45 100644 --- a/src/main/java/com/rosetta/im/packet/Packet0Handshake.java +++ b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java @@ -26,6 +26,11 @@ public class Packet0Handshake extends Packet { * Публичный и приватный ключи клиента */ private String publicKey; + /** + * Приватный ключ клиента + * Это не совсем приватный ключ, а лишь необратимо зашифрованная его версия + * для идентификации клиента на сервере. + */ private String privateKey; /** * Версия протокола клиента @@ -80,4 +85,52 @@ public class Packet0Handshake extends Packet { ); } + public String getPublicKey() { + return publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public int getProtocolVersion() { + return protocolVersion; + } + + public int getHeartbeatInterval() { + return heartbeatInterval; + } + + public Device getDevice() { + return device; + } + + public HandshakeStage getHandshakeStage() { + return handshakeStage; + } + + public void setHandshakeStage(HandshakeStage handshakeStage) { + this.handshakeStage = handshakeStage; + } + + public void setDevice(Device device) { + this.device = device; + } + + 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; + } + } diff --git a/src/main/java/com/rosetta/im/protocol/Client.java b/src/main/java/com/rosetta/im/protocol/Client.java index 58b04a0..a285afe 100644 --- a/src/main/java/com/rosetta/im/protocol/Client.java +++ b/src/main/java/com/rosetta/im/protocol/Client.java @@ -7,40 +7,133 @@ import org.java_websocket.WebSocket; import com.rosetta.im.protocol.util.StringUtil; +/** + * Клиент, подключенный к серверу. + */ public class Client { public WebSocket socket; public String clientId; public Map clientData; + /** + * Интервал отправки heartbeat пакетов в миллисекундах. + */ + public long heartbeatInterval = 0; + /** + * Время последнего полученного heartbeat в миллисекундах. + */ + private volatile long lastHeartbeatTime; - public Client(WebSocket socket) { + /** + * Создает нового клиента с указанным сокетом. + * Этот метод используется внутри протокола для управления подключениями клиентов. + * @param socket Веб-сокет клиента. + * + */ + public Client(WebSocket socket, long heartbeatInterval) { this.socket = socket; this.clientId = StringUtil.randomString(32); this.clientData = new HashMap<>(); + this.heartbeatInterval = heartbeatInterval; + this.lastHeartbeatTime = System.currentTimeMillis(); } + /** + * Проверяет жив ли клиент на основе времени последнего heartbeat. + * Если с момента последнего heartbeat прошло больше, чем указанный интервал, клиент считается неактивным. + * + * Для того чтобы исключить сетевые задержки, проверка умножает интервал на 2. + * @return + */ + public boolean isAlive() { + return (System.currentTimeMillis() - this.lastHeartbeatTime) * 2 <= this.heartbeatInterval * 1000; + } + + /** + * Обновляет время последнего полученного heartbeat на текущее время. + */ + public void updateHeartbeat() { + this.lastHeartbeatTime = System.currentTimeMillis(); + } + + /** + * Получает уникальный идентификатор клиента. + * @return Идентификатор клиента. + */ public String getClientId() { return clientId; } + /** + * Получает данные, связанные с клиентом. + * @return Данные клиента. + */ public Map getClientData() { return clientData; } + /** + * Устанавливает уникальный идентификатор клиента. + * @param clientId Идентификатор клиента. + */ public void setClientId(String clientId) { this.clientId = clientId; } - + + /** + * Устанавливает данные для клиента по указанному ключу. + * @param key Ключ данных. + * @param value Значение данных. + */ public void setData(String key, Object value) { this.clientData.put(key, value); } + /** + * Устанавливает данные клиента. + * @param data Данные клиента. + */ public void setData(Map data) { this.clientData = data; } + /** + * Получает данные клиента по указанному ключу. + * @param key Ключ данных. + * @return Значение данных. + */ public Object getData(String key) { return this.clientData.get(key); } + /** + * Получает веб-сокет клиента. + * @return Веб-сокет. + */ + public WebSocket getSocket() { + return socket; + } + + /** + * Отключает клиента с указанным кодом. + * @param code Код отключения. + */ + public void disconnect(int code) { + this.socket.close(code); + } + /** + * Отключает клиента с указанным кодом отказа. + * @param code Код отказа. + */ + public void disconnect(Failures code) { + this.disconnect(code.getCode()); + } + + /** + * Отключает клиента с неизвестной причиной. + */ + public void disconnect() { + this.disconnect(Failures.UNKNOWN_FAILURE); + } + } diff --git a/src/main/java/com/rosetta/im/protocol/Failures.java b/src/main/java/com/rosetta/im/protocol/Failures.java new file mode 100644 index 0000000..ac36bbd --- /dev/null +++ b/src/main/java/com/rosetta/im/protocol/Failures.java @@ -0,0 +1,55 @@ +package com.rosetta.im.protocol; + +/** + * Перечисление кодов ошибок, используемых в протоколе. + */ +public enum Failures { + /** + * Код ошибки, указывающий на несоответствие данных. + */ + DATA_MISSMATCH(3001), + /** + * Код ошибки, указывающий на незавершенное рукопожатие. + */ + HANDSHAKE_NOT_COMPLETED(3002), + /** + * Код ошибки, указывающий на некорректный пакет. + */ + BAD_PACKET(3003), + /** + * Код ошибки, указывающий на некорректный пакет. + */ + INVALID_PACKET(3003), + /** + * Код ошибки, указывающий на тайм-аут бездействия. + */ + INACTIVITY_TIMEOUT(3004), + /** + * Код ошибки, указывающий на неизвестный тип пакета. + */ + PACKET_ID_FAILURE(3998), + /** + * Код ошибки, указывающий на неизвестный тип пакета. + */ + UNSUPPORTED_PACKET(3998), + /** + * Код ошибки, указывающий на неизвестную ошибку. + */ + UNKNOWN_FAILURE(3999); + + + private final int code; + + Failures(int code) { + this.code = code; + } + + /** + * Получает код ошибки. + * @return Код ошибки. + */ + public int getCode() { + return code; + } + +} diff --git a/src/main/java/com/rosetta/im/protocol/Server.java b/src/main/java/com/rosetta/im/protocol/Server.java index f1bfaf0..3f99a24 100644 --- a/src/main/java/com/rosetta/im/protocol/Server.java +++ b/src/main/java/com/rosetta/im/protocol/Server.java @@ -2,20 +2,27 @@ package com.rosetta.im.protocol; import java.net.InetSocketAddress; import java.nio.ByteBuffer; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; import com.rosetta.im.protocol.packet.Packet; +import com.rosetta.im.protocol.packet.PacketExecutor; import com.rosetta.im.protocol.packet.PacketManager; public class Server extends WebSocketServer { private PacketManager packetManager; + private Settings settings; + private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - public Server(int port, PacketManager packetManager) { - super(new InetSocketAddress(port)); + public Server(Settings settings, PacketManager packetManager) { + super(new InetSocketAddress(settings.port)); + this.settings = settings; this.packetManager = packetManager; } @@ -30,41 +37,97 @@ public class Server extends WebSocketServer { } @Override - public void onMessage(WebSocket arg0, String arg1) { - + public void onMessage(WebSocket socket, String message) { + /** + * Обновляем время последнего полученного heartbeat. + * Так как клиент отпраивл нам пакет, он живой. + */ + Client client = socket.getAttachment(); + client.updateHeartbeat(); } @Override public void onMessage(WebSocket socket, ByteBuffer byteBuffer) { Client client = socket.getAttachment(); - byte[] bytes = byteBuffer.array(); Stream stream = new Stream(bytes); int packetId = stream.readInt16(); - Class packetClass = this.packetManager.getPacketClass(packetId); - if(packetClass == null){ - System.out.println("Received unknown packet with id: " + packetId); + /** + * Обновляем время последнего полученного heartbeat. + * Так как клиент отпраивл нам пакет, он живой. + */ + client.updateHeartbeat(); + + if(!this.packetManager.hasPacketSupported(packetId)){ + /** + * Если пакет не поддерживается, отключаем клиента с соответствующим кодом ошибки. + */ + client.disconnect(Failures.UNSUPPORTED_PACKET); return; } + if(!this.packetManager.hasExecutorDelegated(packetId)){ + /** + * Если для пакета не назначен обработчик, отключаем клиента с соответствующим кодом ошибки. + */ + client.disconnect(Failures.UNSUPPORTED_PACKET); + return; + } + Class packetClass = this.packetManager.getPacketClass(packetId); + try { Packet packet = packetClass.getConstructor().newInstance(); packet.packetId = packetId; + /** + * Читаем данные пакета из потока. + */ packet.read(stream); - System.out.println("Received packet: " + packetClass.getSimpleName()); + /** + * Получаем обработчик пакета и вызываем его метод обработки. + */ + Class executorClass = this.packetManager.getExecutors().get(packetId); + PacketExecutor executor = executorClass.getConstructor().newInstance(); + executor.onPacketReceived(packet, client); } catch (Exception e) { - e.printStackTrace(); + System.out.println("Error while processing packet " + packetClass.getName()); + System.out.println(e.getStackTrace()); } } @Override public void onOpen(WebSocket socket, ClientHandshake arg1) { - Client client = new Client(socket); + /** + * Создаем нового клиента при открытии соединения. + * Передаем интервал heartbeat из настроек сервера. + * Если клиент не отправляет heartbeat в указанный интервал, его можно отключить. + */ + Client client = new Client(socket, this.settings.heartbeatInterval); socket.setAttachment(client); } @Override public void onStart() { - + /** + * Настраиваем планировщик для проверки активности клиентов. + */ + this.inactivityShedulerTweaker(); + + int port = this.getPort(); + System.out.println("\u001B[32mServer started at x.x.x.x:" + port + "\u001B[0m"); + } + + /** + * Планировщик для проверки активности клиентов. + * Если планировщик обнаруживает неактивного клиента, он отключает его с соответствующим кодом ошибки. + */ + public void inactivityShedulerTweaker() { + this.scheduler.scheduleAtFixedRate(() -> { + for(WebSocket socket : this.getConnections()) { + Client client = socket.getAttachment(); + if(!client.isAlive()) { + client.disconnect(Failures.INACTIVITY_TIMEOUT); + } + } + }, this.settings.heartbeatInterval, this.settings.heartbeatInterval, TimeUnit.MILLISECONDS); } } diff --git a/src/main/java/com/rosetta/im/protocol/Settings.java b/src/main/java/com/rosetta/im/protocol/Settings.java new file mode 100644 index 0000000..dbf9163 --- /dev/null +++ b/src/main/java/com/rosetta/im/protocol/Settings.java @@ -0,0 +1,18 @@ +package com.rosetta.im.protocol; + +public class Settings { + /** + * Порт сервера + */ + public int port = 8881; + /** + * Интервал отправки heartbeat пакетов в секундах. + * Если клиент не отправляет heartbeat пакеты в течение этого времени, сервер может считать его отключенным. + */ + public long heartbeatInterval = 30; + + public Settings(int port, long heartbeatInterval) { + this.port = port; + this.heartbeatInterval = heartbeatInterval; + } +} diff --git a/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java b/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java index bca1a0a..c1bac51 100644 --- a/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java +++ b/src/main/java/com/rosetta/im/protocol/packet/PacketExecutor.java @@ -1,5 +1,7 @@ package com.rosetta.im.protocol.packet; +import com.rosetta.im.protocol.Client; + public interface PacketExecutor { - public void onPacketReceived(Class packet); + public void onPacketReceived(Packet packet, Client client); } diff --git a/src/main/java/com/rosetta/im/protocol/packet/PacketManager.java b/src/main/java/com/rosetta/im/protocol/packet/PacketManager.java index cabd5dd..2118fe5 100644 --- a/src/main/java/com/rosetta/im/protocol/packet/PacketManager.java +++ b/src/main/java/com/rosetta/im/protocol/packet/PacketManager.java @@ -2,20 +2,48 @@ package com.rosetta.im.protocol.packet; import java.util.HashMap; +/** + * Менеджер сетевых пакетов и их обработчиков. + */ public class PacketManager { private HashMap> packets; + private HashMap> executors; public PacketManager() { this.packets = new HashMap<>(); + this.executors = new HashMap<>(); } + /** + * Регистрирует пакет с указанным ID. + * @param packetId ID пакета + * @param packet Класс пакета + */ public void registerPacket(int packetId, Class packet) { this.packets.put(packetId, packet); } - public boolean isPacketRegistred(Packet packet) { - return this.packets.containsValue(packet.getClass()); + /** + * Проверяет, зарегистрирован ли обработчик для пакета с указанным ID. + * @param packetId ID пакета + * @return true, если обработчик зарегистрирован, иначе false. + */ + public boolean hasExecutorDelegated(int packetId) { + return this.executors.containsKey(packetId); + } + + /** + * Проверяет, поддерживается ли пакет с указанным ID. + * @param packetId ID пакета + * @return true, если пакет поддерживается, иначе false. + */ + public boolean hasPacketSupported(int packetId) { + return this.packets.containsKey(packetId); + } + + public HashMap> getExecutors() { + return this.executors; } public Integer getPacketIdByClass(Class packetClass) { @@ -31,5 +59,33 @@ public class PacketManager { return this.packets.get(packetId); } + /** + * Регистрирует обработчик пакета с указанным ID. + * @param packetId ID пакета + * @param executor Обработчик пакета + */ + public void registerExecutor(int packetId, Class executor) { + if (this.executors == null) { + this.executors = new HashMap<>(); + } + this.executors.put(packetId, executor); + } + + /** + * Возвращает общее количество зарегистрированных обработчиков пакетов. + * @return Количество обработчиков пакетов. + */ + public Integer totalExecutors() { + return this.executors.size(); + } + + /** + * Возвращает общее количество зарегистрированных пакетов. + * @return Количество пакетов. + */ + public Integer totalPackets() { + return this.packets.size(); + } + } diff --git a/target/classes/com/rosetta/im/Main.class b/target/classes/com/rosetta/im/Main.class index 33955d8f21ce53ba53052ecf16f68be8b5e93ec1..4dd96c5d9dfd58bfac660d1f0c7dc7c79e54f3d4 100644 GIT binary patch delta 436 zcmZvYze>YU6vn@srcD}4wYE{yYFZPu|Bcn^U=hSksV+hxZdF7I)<|X2lU8mP~ z4m^)*NMkO6DCQX&rQky{f@euL%N-#Y31ksth@X?w=Y#I8f<=Zx@F``BocC_7{3ykX z&(x4ZK?E%`L`#Q9<&FZI!6dJl)L>y%h-(a|f|64v?X5pzMi4EO5-1B|)aTs+53Z%N zsx06Pdi$U0TMVjLM@+(CiQw3z3f`nUrUd(u{QVCB86$_$iRkyb$X&L+I>n8CLB~y jrAkGOPQ3y~4K^7ain8gxfd(Q}$FNDQGU-jSOW6JeOCD4H delta 480 zcmY+A-)a*<7{!0HvE6lDYfOlmRGV0}O-#+!`ln#=${Pz6iiJXjLWg#UYm-gc9Saqt z&mj8@K0v)l!Ge12Yv>F30^+yHg?e*_Z|2PJaK4Y;PuKtc`|}szDa#-HUpsTVgMOF{ zQe&+SqkedxclV7A*GI?Va~0|2iM=>)_fI*Ah~loa%bqx|vLDWo^0Mth{rDZ{D9Bcu zXB4+iDXp~&rSg!<8P%=JvnTCSre7=!nfb)c()^ItiFh4+(HfJYJ{}ji#1zw_bxO=r fmK$U(BQ7C}GI=uJX9*>~i#*_=P!x}Hgu~-Mjy`R! diff --git a/target/classes/com/rosetta/im/executors/Executor0Handshake.class b/target/classes/com/rosetta/im/executors/Executor0Handshake.class index 1b3aeadec4f12064f7e93fb468c3c9ee1484aa2e..bd5c6761c814e6750793538a62ce1a3c54e3b428 100644 GIT binary patch delta 488 zcma)2yG{a85IqAd=(>m?p!mcWEHP%SEFe}U1{Dbj#{RP2zzQs6SxBt-0874v#zYHa zW#JcD8D}9r5*v#-^SEcuoZMICF{HkIJUs(AhCWuqe8+BYc0#XkavW*o-5so zfiPOat{YX?vFv)mJ)(cdUVMB`;`%;PeQ=GnDlP=EM^r{o5g7w()hJpqKvV zw7_fKA9u08-Uw><+YW(h9A`YNcKVJfPp#2AUD3?w(w7>t7JuSB2qDgi{TIZ_2M>tn z1B}ag#eWnW^Mel>V-gCZ#u&yN<`G2%aV&5L6sH0_jc~_OSj8I4deu5MSV3XtCerZp cu8J*eBY^cN!w#1U3lVm)hal$+ulbPu1kJf<*8l(j delta 301 zcmdnVevFmt)W2Q(7#J9A8KgFH@iLl=X!v9$mL=-vB<7{-JLe=87h7wFF*0xlXQt;R zmXsEyGBVgu zn&?Gud-5B2@L&^r@t}7o6zbdM!ZP#b_uH9y`*HN=vDlBlU%vsk0&@rf zhH0l6OgO(;Z13W`;KN9bTg9D9U9T4SE<@A}O}(vIT*MP1HY}rR)XUs#=|+PgPRScw zGp!2OteXvsn{BPm5T^7pZ|hagkW^Ev&Y!@k6FG))w_Iziw>Gp*Ub3_rozJL_5zHpW zTi&Cr>TCzk9Zi+VNXG}pDOCv9a_&Lg<-8qCP`YSJ-9|;SKT^bv4<>fL2cNJpC#fuj zrx8X5W4Pq#%7@ft4AaJ9Sio^CB8era$YT~)k;65tU>SFD9gnc$WJ2%H7pQubs&7;E z8dcxHSrn0`npxZ`tS>1v0T;DiiwUw9q7*SFoDdR}f^ z_obhr)}#CIqzrHQ!W+=L@B&R40BnTPnSD&|^~N4?(`Z70DHI72hq_yLZzPIQk}y2O z=Kr#eYhHHkkgGkwWgijO%23CQM|N@_30byFvgZS2BlP9Ejcxh6;LJ2k1H?MKPSuHknHUMnbMVQiz{pX1B08TWB>pF delta 159 zcmWN}y$-=(7>41yRn;zZ!pNXOA`yds+9p!eU%HEgI047O_DwdU#AG!%00&_=IRo)d z&vHHYJ9kYxuixVd4A`a6HC%Ntvg)fj_vX=1fw|EV>SFqK5gV5b4fQe4l@=Cl96IFb zQlLkfK6R<|7!u+U_sQT6b6P2olNYqeQKZT zdtdtxd}zChmv*iGfc}#HimvW+&LkNFsCmdabI$kev%mda{`v3EzX4oAEscc05!>G| z1Ha~kp=G)o=4RlBzU_PFxaT_6FpUm@o;B;CWqMY1#hk0GId&+}S@Z36hnpiaez0On zgZtk)6@6+hI+e1z9~J1b70}eOz`mIlP&o|T>I(0(`u@5VS_0kLq8_@QdCS`5b*AjD zRIRWcI09#1zP-2|E_R}sj$S6l-c`p6!ir;sQ`OK39$KD&nC7`>)xycf-CcKsZ**RC zt8RE%V4(Qw$fk!E1Ukn3WhaXydJXiTPas(wUdTXTzkxjV$lcopQs~U$5Z+1Su)xr6 zf~%mlEc1-tz!AJFU<6im+27FT0)54);aHH7it?SK2GZzC;~3*oGAq1EtF}rZvlu|H zv^rrRgKQQ>l+qY}DYfNCwb0oxFoKhWu&_#A6x*Y&a@lIu2!{!d(3?8RNE%Nz4&P8H)j!N0+rIzhx_PwOsGT$urA0ask1Hzs zcy1$#Qlq;D=0RRbIV znk;wq3j1m*%NAZUup~(gy#B!qpW{@hVx1j7KYMd_ZgKYh#Mso#{GA&TisiS23g>v) zuLrg>;mSdf)AfH^mJp88D#=DpapfrFj+D@)Rt_V6M1$h8IEsZuvCz@&oQ)xQ@_zA0h9j& z>d~VF>Ejd};1kENAIG^H#1Ky4H2>eDT|v<`aR%@6iw`Q=LhU5M1POhB4&1s_A?QmuaQbaz%+B2N`S! zncNZND#juP5=^q~lnE=e5~5?cO?f|62>sfsDSW4@6UqJ>#v`mbDk7^X&u9imPQJkP zBo^Btmsm{q6r|2&z6qJfG^;?$k;hHU@IeWFi>r=yRz-W8-%Y}U5?yCc6ShdWvJ2rk zRnWKTp|jhGIkgiE@lF)Nl?XkjLU@640_}>f#o%*I^0|n-z#=}2z~YHH&<+zQ7|o=W zUABJYJRW0m;Lr_&Z%6xy1@br+W2^kXp;rRI|0{$y(*OVf delta 792 zcmZ{i%}x_h6o9`w)6UR#Mp_C5tt~B}v@^wmzlc~wT(BXMn4m6ebWvlJMR8+d<0FuH z4iimm5KVjleFkIPxlvaW6 zOik?8K4h%r&cjZ(rKr|+H_Lu~OOaT6boX8wlbnOgh{CMZw^ArZ9gt4m&8JF%bHSV@WiV2Y{9x5k#Sh6xg(N>yvyDm zSr9>D^$ygA7$dVu@W+U?CDKW~l{-#OHa6(qX3~QA< zij&O7`I@VG1Crh=iZ3K8;)ZcBt~rjLCu5GHMqQ#ZI@V)2_-+?^p-Xh8_|G#wjW}aV z&W2uASY7dNC274oDUOu`_SV4O3a>-U&T%ScU6*c|!g70JU-29JG)-CG1Eg ziXJds&S|C)IL`$!P|UF?@2I+q`c5`wJuue2H+AnL<5@AT_5P#0L|TxSBz4q8izPi% kEX$7@UHM9k&*&QVX&D^82mb&*GqBIZ_KK^PWH4C!140u|q5uE@ diff --git a/target/classes/com/rosetta/im/protocol/Server.class b/target/classes/com/rosetta/im/protocol/Server.class index ca113bb4a161398c4df983d06c2136df9fa0a937..c49548fd6743d68086c0bfd77083908dba969108 100644 GIT binary patch literal 6559 zcmcIo33wFc8GipHWHy_Dgg`<Y?7nnVv$@PBC(8hI3U`1as1@6YcQ2zd)fKQqdH^x%}%|*|K zy*=Dtk&49hXgVR41^T3wfwUb@1R|ypNn4g~*nzOOBig%9kECtWlE%g(I!~9ci5qcy ztw6Zt{K|^%nAVln5rMKEGpZ}75?JV1W)Qg$KU~bZqESmvr7AEN^AywwG!?@t>#vIW zsHNBm)3OCb$d5T#prR7>eAp6d9r2?S3+01GfyFH@9MdWd%Doq1v4V?9f@xvPqLot7 zge3xXhQ2ok<#5c>wdjy(CInVd^V3AW0s@z)sDNMob*TykKKa*WDrTWvpsFu!=mY8G zm~IVgV+mcLrq7IMi4o0;%irF^GCLM0QJRWI#-$|#t(Znnf;1PnvZZetKIe!W47si( zG-Esvwk=XIxWkF1W{s~TRx_Vw9Judt_iL3j+rc(pn!9!d?9jwTFNcB;q-(oB6?I>8fx890SyraW5lnR+znbhk!HbC z5rs_1tpnk$LqmgGhI`lV9O~-1zIS-0lAS#rl<^%`5kp+SAC0G2s10(1RiJnY&K;KE zjAFNngmh;<-N{y|^?E`dCs(3!gQ3F2guon<()G6|p`}l8mr=Y}#Y^x~0hN)+hKg~h%~l7eS8vo(v3_lW)Oa~w zso)g?b0@(d`E`yGDqe+GGvW7WiL^eri|wc|l-@SVcr9M9;B{FU`3h9L0dHg;(H4Pa zUVF^1Pt#)eCV`F%!Y=DyCGN(XRlEgnWx!3tCEyl4qR02heA`qUx`I#yi*_}ykhP9f z;O%&)f_D@UE4)8t>q!;&;9d09Ow(7dMX+8Cv7y;sge1l9Rq-C&M_h@?fR-ew3JW*4 z$_`k=k@0;h-j5FmEJ$j*buv4m+1uiF%;C6Z*oZwpJdmxUg=VSv z5DwFz32AAoZc_@gC+!}#G?_3>EXoDKvP-=Q zUA8T<=YE`!7Mz@-H=A5+KK`*>Q^+AZrB|?$%T0u&WVBnhE2q-W)SKuRdjWe>rON@Y zUd_$3)-5xX=I&tn7bQp`kBEcrO}?I3U-q}0nV%N7X`vvHb%oQ|{)y~F7MyDEn)VA* z5L`>qJ^$j za(MruiZ9{IEI6_3oFa>n#Y#o6HpzbXRoUOa#`bs~?VH@G>q!;gkPMv7yHNg2wPL#2 zv$%CkBGVui7H2Af@2L2$oT(b(h8D5od*b%~uy=DB-mB9)OYZuK4hS`T z_9L12Kb8rRo%5Y!^HQ+}CHSd|pW)}MHddM#bG$0XDQ_OnFA7`Ih9O7#U%+Y5xYQ|jC|HT#{sw&Fl{9k71`{e8{%2lBV9~l%+btTyGoViub zm%^`#N@sEm4RrMkhekrf+jkC!`g;er4oeNQRWZjo$f<6C!dPj;X4;DA8{+Z;Sm|c+CcJ~)_zi2)Ct_3 zA3$%nD^MnRLQgI2GtJ%UiTtwW@jkCq6&xI9F`Q<5e&5M9lXFG%Xo*Ca@d#FxiyEJ3 zAg`V4Vbk6iL1^OJnFudD?8r@SiEkocjdZzj^omtU7oa9ihjOrQ((I;vE1Z9wqH%O( z(l0I&ixt89e)Bm9^*KDF=h`k0AJHV15cK5aBY`LW|7gt!Wx=8wpnRkYmYp!<*BNg$ zAK^vS=w)UJzJK@&daD$%ln+>0LcEb!POH>YifH5Pd=6mKkwg*8v+q*!*;lZYt;l5* zmN2DDIaZaje)63M|0C7vZ;jAyNsIpR@UE0%gwr=C)Iqv%UQ&s*j`YI2zi@ z9>$_$Xl`pihNW%Gk73ziM@9=j>nIthM-^(Z01a4#h1A=KRz7WVrJ)V&`~@^S+J*C% zw5No6m!ree9_Di?OH2wc=aLj7g+0Sv%n9R*KLnh0ft6HVB9G#7ta6m{0>u+$d~zjL z^HaKa9sf%>&QWk_p0%Qgf~y>Buf{c=u$}xaqp+&>6X+B;jdcSZ8FWwDKF873L6}$Y z6si?vR&xa~Jla`lo_bhBgh-U?^#wJ%;$x{BVq_Q)U%9dwveP`JzY~J2b zb{yf(Sq-x?7+GB|jc7ZHWwqrQ>^OwVjw5(>2D=VtpWQsovt*~zpp-s>TQW%TYi|bo zPvc;xufbPaehklT@JZRvlM*s`QEmCdcv%LoIgML7D;g@M9=p5K-{5cXb)3MVQrw58 zPTvu{twG7)-6!#0aAy9X{O!x&NCpodrhnVVg#AyK6Q^EiYz8Xm*j04!lTigw}*O9veM|oBM1a8C^_;y0F z=vI=QIOZa7CKrJ-IE4>85qQjtKsBTK2p*+njW~vn@YgKb@_@X#lDylo7azsPNNIz1 zeVkf+)Vvs<l77@$O(@&S{G1@BWS$_)awzo5MOON8raeQT< zoss!^38wONoM^|G!0|ku*5^>F&!JS$T>B<{sHQ(zo_-6xp4Jhr$ULlT%i!Dm|GhN~ zcH>F>&(t=1+$hfqbXa+xzf{d195Xo2%TzfJBgnF zo_pTc5&Yr=ey3pDRDHKG8h0`px3NIo<>-@zPnIP|Uv-Hr(VmujXo4i?^JwR}8r{X*K>>!i= z0Mq+U#|XDdUgJ5vfVI>u3QJfvT!YdMpqM5%h5zEA7Lgtn1L9L=)bfDqC7mpB4 literal 3828 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 delta 1038 zcmYjPOHWfl7(LV1ZOgRfWyMydl}Bj-D~M0TA{7Clh@c?gg9}`1p()y4Ojx0b8<%EQ zxG*s>al-olBt}Y)#e!@O+qz?`odFAn9<5{5>W748x81|QgoS22G*e9xFV5f)yc97UMh#&X8IBmZvou9>t8hm?p~C+KzLq=!6B5=8 z3G1W*3GO0L2^lbG+;>84mBa>>`62hp8c zCw@vwI_UsVX;68KjU_5ha2-@$GwStW3e%)Tno!g)YvEGuF4gW)ts16Xl*6KcDM(;Np=W|RYP*jIrPXGP)#4OO q$2|XUC=ZpkkV+4FR(rrRPf3|)?|MwojQ)p6(pSE!emug0&G8S)NttT^