From b914364c49b5b89bcf4ac3019c620fda15833857 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Fri, 10 Jan 2025 23:47:19 +0300 Subject: [PATCH 01/25] Auto-close DB and fix server closing algorithm --- src/main/java/net/pixtaded/crab/server/CrabServer.java | 4 ++-- src/main/java/net/pixtaded/crab/server/Database.java | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/pixtaded/crab/server/CrabServer.java b/src/main/java/net/pixtaded/crab/server/CrabServer.java index 4874014..ea6eab1 100644 --- a/src/main/java/net/pixtaded/crab/server/CrabServer.java +++ b/src/main/java/net/pixtaded/crab/server/CrabServer.java @@ -60,9 +60,9 @@ public class CrabServer implements Crab { } } - public void stop() { + public synchronized void stop() { + isStopped = true; try { - isStopped = true; if (socket != null) socket.close(); if (serverSocket != null) serverSocket.close(); } catch (IOException e) { diff --git a/src/main/java/net/pixtaded/crab/server/Database.java b/src/main/java/net/pixtaded/crab/server/Database.java index d328f9b..61986d5 100644 --- a/src/main/java/net/pixtaded/crab/server/Database.java +++ b/src/main/java/net/pixtaded/crab/server/Database.java @@ -6,7 +6,7 @@ import java.sql.*; import java.util.Date; import java.util.Locale; -public class Database { +public class Database implements AutoCloseable { private Connection connection; private String logs = ""; @@ -68,4 +68,10 @@ public class Database { else return logs.getBytes().length; } + @Override + public void close() throws SQLException { + if (connection != null && !connection.isClosed()) { + connection.close(); + } + } } From 162c4bdf53dbac89e484db75d23344a6fe0f2e28 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Fri, 10 Jan 2025 23:58:22 +0300 Subject: [PATCH 02/25] bump version to 1.0.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3f1bb2d..81e87c6 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'net.pixtaded' -version = '1.0-SNAPSHOT' +version = '1.0.1' repositories { mavenCentral() From 6dace63cb043c0e137222a4fd9b12bd2126b5ec5 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sat, 11 Jan 2025 16:28:43 +0300 Subject: [PATCH 03/25] Fix some vladcode --- src/main/java/net/pixtaded/crab/client/CrabClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index 52ed5db..c435438 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -68,7 +68,7 @@ public class CrabClient implements Crab { System.out.print("Enter your nickname (leave empty for no nickname): "); nickname = scanner.nextLine(); - if (nickname != "") + if (!nickname.isEmpty()) nickname = "<" + nickname + "> "; } From 7a116c6789e2c50ec449f0e4c44278ca95d6d15d Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sat, 11 Jan 2025 19:51:38 +0300 Subject: [PATCH 04/25] Make CRAB work much more smoother (clear screen only after receiving new logs) --- src/main/java/net/pixtaded/crab/client/CrabClient.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index c435438..5ae46d1 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -82,9 +82,7 @@ public class CrabClient implements Crab { private void communicate() throws IOException { Scanner scanner = new Scanner(System.in); String message; - while (true) { - System.out.print("\033[H\033[2J"); getLogs(); System.out.print("Enter a message (or type '/exit' to exit): "); message = scanner.nextLine(); @@ -114,6 +112,7 @@ public class CrabClient implements Crab { lastBufferLength = Integer.parseInt(new String(buffer).trim()); } case LOGS -> { byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength); + System.out.print("\033[H\033[2J"); System.out.print(Sanitizer.sanitizeString(new String(bytes, StandardCharsets.UTF_8), false)); } default -> { } From bdf7e6bbae53037d811228d0d7a0fdf342055acf Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sat, 11 Jan 2025 19:52:17 +0300 Subject: [PATCH 05/25] bump version to 1.0.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 81e87c6..0abf150 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'net.pixtaded' -version = '1.0.1' +version = '1.0.2' repositories { mavenCentral() From 57b20f69cd588f3881227e3dae3e840b597d1160 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 12 Jan 2025 02:05:34 +0300 Subject: [PATCH 06/25] Implement message caching (Only send LOGS packet to server when the LOGS_SIZE returned by server is different from cached one) --- .../java/net/pixtaded/crab/client/CrabClient.java | 15 ++++++++++++--- src/main/java/net/pixtaded/crab/client/Logs.java | 4 ++++ .../java/net/pixtaded/crab/server/CrabServer.java | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/pixtaded/crab/client/Logs.java diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index 5ae46d1..6355e80 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -18,6 +18,7 @@ public class CrabClient implements Crab { private BufferedReader in; private int lastBufferLength = 0; private String nickname; + private Logs cache = new Logs(0, ""); public CrabClient() { this.nickname = ""; @@ -111,9 +112,12 @@ public class CrabClient implements Crab { int response = in.read(buffer); lastBufferLength = Integer.parseInt(new String(buffer).trim()); } case LOGS -> { - byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength); - System.out.print("\033[H\033[2J"); - System.out.print(Sanitizer.sanitizeString(new String(bytes, StandardCharsets.UTF_8), false)); + if (cache.sizeInBytes() != lastBufferLength) { + byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength); + cache = new Logs(lastBufferLength, new String(bytes, StandardCharsets.UTF_8)); + } + clearScreen(); + System.out.print(Sanitizer.sanitizeString(cache.content(), false)); } default -> { } } @@ -135,4 +139,9 @@ public class CrabClient implements Crab { sendPacket(LOGS_SIZE, ""); sendPacket(LOGS, ""); } + + private void clearScreen() { + System.out.print("\033[H\033[2J"); + } + } diff --git a/src/main/java/net/pixtaded/crab/client/Logs.java b/src/main/java/net/pixtaded/crab/client/Logs.java new file mode 100644 index 0000000..479e41c --- /dev/null +++ b/src/main/java/net/pixtaded/crab/client/Logs.java @@ -0,0 +1,4 @@ +package net.pixtaded.crab.client; + +public record Logs(int sizeInBytes, String content) { +} diff --git a/src/main/java/net/pixtaded/crab/server/CrabServer.java b/src/main/java/net/pixtaded/crab/server/CrabServer.java index d5705a4..3af44dc 100644 --- a/src/main/java/net/pixtaded/crab/server/CrabServer.java +++ b/src/main/java/net/pixtaded/crab/server/CrabServer.java @@ -12,7 +12,7 @@ public class CrabServer implements Crab { private Socket socket; private boolean isStopped = false; private int port; - private Database db; + private final Database db; public CrabServer() { this.db = new Database("data.db"); From 6e1d843f790e263c3022fd990155a90ae96b20d2 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 12 Jan 2025 02:06:39 +0300 Subject: [PATCH 07/25] Bump the project's version to 1.0.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0abf150..d949b8f 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'net.pixtaded' -version = '1.0.2' +version = '1.0.3' repositories { mavenCentral() From 5c55999771fdfa7b492db29f4d5815e0e71d95ee Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 12 Jan 2025 04:00:34 +0300 Subject: [PATCH 08/25] Revamp network logic. Closes #2 --- src/main/java/net/pixtaded/crab/client/CrabClient.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index 6355e80..4336866 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -38,7 +38,6 @@ public class CrabClient implements Crab { try { if (this.serverAddress == null) setup(); - connect(); communicate(); } catch (IOException e) { System.err.println("Error connecting to the server: " + e.getMessage()); @@ -97,12 +96,14 @@ public class CrabClient implements Crab { } private void sendPacket(byte PID, String argument) throws IOException { + connect(); String formattedMessage = String.valueOf((char) PID) + argument + "\n"; out.print(formattedMessage); out.flush(); receiveResponse(PID); + closeConnection(); } private void receiveResponse(byte PID) throws IOException { @@ -121,8 +122,6 @@ public class CrabClient implements Crab { } default -> { } } - closeConnection(); - connect(); } private void closeConnection() { From a7ad8770c994bab1d709e37e2cb4b70a081fd295 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 12 Jan 2025 04:03:34 +0300 Subject: [PATCH 09/25] Add a terrible and hacky implementation of scrolling and bump the project's version to 1.0.4. --- build.gradle | 2 +- src/main/java/net/pixtaded/crab/client/CrabClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index d949b8f..c368e8d 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'net.pixtaded' -version = '1.0.3' +version = '1.0.4' repositories { mavenCentral() diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index 4336866..6e7d023 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -140,7 +140,7 @@ public class CrabClient implements Crab { } private void clearScreen() { - System.out.print("\033[H\033[2J"); + System.out.print("\033[999999S\033[H\033[2J"); } } From c2e8927c4209822d57ffc95e0d91e7b3089e9674 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 12 Jan 2025 15:06:43 +0300 Subject: [PATCH 10/25] Fix server throwing an ArrayOutOfBoundsException when someone tries to leave --- src/main/java/net/pixtaded/crab/server/ServerThread.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/pixtaded/crab/server/ServerThread.java b/src/main/java/net/pixtaded/crab/server/ServerThread.java index 03006e4..2c13324 100644 --- a/src/main/java/net/pixtaded/crab/server/ServerThread.java +++ b/src/main/java/net/pixtaded/crab/server/ServerThread.java @@ -30,10 +30,13 @@ public class ServerThread implements Runnable { public void run() { try { byte[] PID = input.readNBytes(1); + if (PID.length == 0) { + socket.close(); + return; + } switch (PID[0]) { case MESSAGE -> { server.getDb().logMessage(new Date(), socket.getInetAddress().getHostAddress(), new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim()); - socket.close(); } case LOGS -> { respond(server.getDb().getLogs()); } case LOGS_SIZE -> { @@ -42,6 +45,7 @@ public class ServerThread implements Runnable { System.out.println("PID not implemented: " + PID[0]); } } + socket.close(); } catch (Exception e) { throw new RuntimeException(e); } @@ -50,7 +54,6 @@ public class ServerThread implements Runnable { private void respond(byte[] data) throws IOException { socket.getOutputStream().write(data); socket.getOutputStream().flush(); - socket.close(); } private void respond(String utf8) throws IOException { From e96b02357d39073f9e863f28ba9b9638448b2a20 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 12 Jan 2025 17:26:21 +0300 Subject: [PATCH 11/25] Add serverside caching and improve sanitizing. --- .../net/pixtaded/crab/client/CrabClient.java | 1 + .../crab/{client => common}/Logs.java | 2 +- .../net/pixtaded/crab/common/Sanitizer.java | 6 +++- .../net/pixtaded/crab/server/CrabServer.java | 3 ++ .../net/pixtaded/crab/server/Database.java | 16 +++------ .../pixtaded/crab/server/ServerThread.java | 33 +++++++++++++++++-- 6 files changed, 45 insertions(+), 16 deletions(-) rename src/main/java/net/pixtaded/crab/{client => common}/Logs.java (62%) diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index 6e7d023..14a60ce 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -1,6 +1,7 @@ package net.pixtaded.crab.client; import net.pixtaded.crab.common.Crab; +import net.pixtaded.crab.common.Logs; import net.pixtaded.crab.common.Sanitizer; import java.io.*; diff --git a/src/main/java/net/pixtaded/crab/client/Logs.java b/src/main/java/net/pixtaded/crab/common/Logs.java similarity index 62% rename from src/main/java/net/pixtaded/crab/client/Logs.java rename to src/main/java/net/pixtaded/crab/common/Logs.java index 479e41c..cbccff4 100644 --- a/src/main/java/net/pixtaded/crab/client/Logs.java +++ b/src/main/java/net/pixtaded/crab/common/Logs.java @@ -1,4 +1,4 @@ -package net.pixtaded.crab.client; +package net.pixtaded.crab.common; public record Logs(int sizeInBytes, String content) { } diff --git a/src/main/java/net/pixtaded/crab/common/Sanitizer.java b/src/main/java/net/pixtaded/crab/common/Sanitizer.java index be553fb..2a38145 100644 --- a/src/main/java/net/pixtaded/crab/common/Sanitizer.java +++ b/src/main/java/net/pixtaded/crab/common/Sanitizer.java @@ -2,11 +2,15 @@ package net.pixtaded.crab.common; public class Sanitizer { public static String sanitizeString(String s, boolean sanitizeNewlines) { - String sanitized = s.replaceAll("\033", ""); + String sanitized = s.replaceAll("[\010\015\033]", ""); if (sanitizeNewlines) { sanitized = sanitized.replaceAll("\n", "\\\\n"); if (!s.endsWith("\n")) sanitized += '\n'; } return sanitized; } + + public static String formatMessage(long timeMillis, String address, String content) { + return String.format("[%td.%1$tm.%1$tY %1$tR] {%s} %s", timeMillis, address, content); + } } diff --git a/src/main/java/net/pixtaded/crab/server/CrabServer.java b/src/main/java/net/pixtaded/crab/server/CrabServer.java index 3af44dc..16ec627 100644 --- a/src/main/java/net/pixtaded/crab/server/CrabServer.java +++ b/src/main/java/net/pixtaded/crab/server/CrabServer.java @@ -1,5 +1,6 @@ package net.pixtaded.crab.server; import net.pixtaded.crab.common.Crab; +import net.pixtaded.crab.common.Logs; import java.io.IOException; import java.net.ServerSocket; @@ -13,6 +14,7 @@ public class CrabServer implements Crab { private boolean isStopped = false; private int port; private final Database db; + public Logs cache = new Logs(0, ""); public CrabServer() { this.db = new Database("data.db"); @@ -28,6 +30,7 @@ public class CrabServer implements Crab { try { if (this.port == 0) setup(); + this.cache = getDb().getLogs(); listen(); } catch (IOException e) { if (!isStopped) System.err.println("Error starting server: " + e.getMessage()); diff --git a/src/main/java/net/pixtaded/crab/server/Database.java b/src/main/java/net/pixtaded/crab/server/Database.java index 61986d5..b88c13f 100644 --- a/src/main/java/net/pixtaded/crab/server/Database.java +++ b/src/main/java/net/pixtaded/crab/server/Database.java @@ -1,15 +1,14 @@ package net.pixtaded.crab.server; +import net.pixtaded.crab.common.Logs; import net.pixtaded.crab.common.Sanitizer; import java.sql.*; import java.util.Date; -import java.util.Locale; public class Database implements AutoCloseable { private Connection connection; - private String logs = ""; public Database(String dbName) { try { @@ -50,22 +49,17 @@ public class Database implements AutoCloseable { } } - public String getLogs() { + public Logs getLogs() { StringBuilder s = new StringBuilder(); try (ResultSet rs = query("SELECT time, address, msg FROM messages")) { while (rs.next()) { - s.append(String.format("[%td.%1$tm.%1$tY %1$tR] {%s} %s", rs.getLong(1), rs.getString(2), rs.getString(3))); + s.append(Sanitizer.formatMessage(rs.getLong(1), rs.getString(2), rs.getString(3))); } } catch (SQLException e) { throw new RuntimeException(e); } - logs = s.toString(); - return logs; - } - - public int getLogSize() { - if (logs.isEmpty()) return 0; - else return logs.getBytes().length; + String logsString = s.toString(); + return new Logs(logsString.isEmpty() ? 0 : logsString.getBytes().length, logsString); } @Override diff --git a/src/main/java/net/pixtaded/crab/server/ServerThread.java b/src/main/java/net/pixtaded/crab/server/ServerThread.java index 2c13324..ee0ce2d 100644 --- a/src/main/java/net/pixtaded/crab/server/ServerThread.java +++ b/src/main/java/net/pixtaded/crab/server/ServerThread.java @@ -1,5 +1,8 @@ package net.pixtaded.crab.server; +import net.pixtaded.crab.common.Logs; +import net.pixtaded.crab.common.Sanitizer; + import java.io.*; import java.net.Socket; import java.nio.charset.StandardCharsets; @@ -36,11 +39,19 @@ public class ServerThread implements Runnable { } switch (PID[0]) { case MESSAGE -> { - server.getDb().logMessage(new Date(), socket.getInetAddress().getHostAddress(), new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim()); + Date date = new Date(); + String msg = new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim(); + String address = socket.getInetAddress().getHostAddress(); + + new Thread(new LogDBThread(date, address, msg)).start(); + + String s = Sanitizer.sanitizeString(msg, true); + String newContent = server.cache.content() + Sanitizer.formatMessage(date.getTime(), address, s); + server.cache = new Logs(newContent.getBytes().length, newContent); } case LOGS -> { - respond(server.getDb().getLogs()); + respond(server.cache.content()); } case LOGS_SIZE -> { - respond(String.valueOf(server.getDb().getLogSize())); + respond(String.valueOf(server.cache.sizeInBytes())); } default -> { System.out.println("PID not implemented: " + PID[0]); } @@ -59,4 +70,20 @@ public class ServerThread implements Runnable { private void respond(String utf8) throws IOException { respond(utf8.getBytes(StandardCharsets.UTF_8)); } + + private class LogDBThread implements Runnable { + + Date date; + String msg; + + public LogDBThread(Date date, String address, String msg) { + this.date = date; + this.msg = msg; + } + + @Override + public void run() { + server.getDb().logMessage(date, socket.getInetAddress().getHostAddress(), msg); + } + } } From 7ba5bfbf9ea00ce634851d7bc398b05f1ac21d4c Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 12 Jan 2025 18:07:15 +0300 Subject: [PATCH 12/25] Add shadowJar instead of application, bump the project's version to 1.0.5 --- build.gradle | 24 ++++++++++++++----- gradle.properties | 1 + .../pixtaded/crab/server/ServerThread.java | 8 ++++--- 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 gradle.properties diff --git a/build.gradle b/build.gradle index c368e8d..b41e4c5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,31 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + plugins { id 'java' - id 'application' + id 'com.gradleup.shadow' version '9.0.0-beta4' } group = 'net.pixtaded' -version = '1.0.4' +version = projectVersion repositories { mavenCentral() } -application { - mainClass = 'net.pixtaded.crab.Main' -} - dependencies { implementation('org.xerial:sqlite-jdbc:3.47.1.0') +} + +tasks.build.dependsOn tasks.shadowJar + +tasks.named('jar', Jar) { + manifest { + attributes 'Main-Class': 'net.pixtaded.crab.Main' + } +} + +tasks.named('shadowJar', ShadowJar) { + archiveBaseName = 'crab' + archiveClassifier = '' + archiveVersion = projectVersion } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..5f67769 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +projectVersion=1.0.5 \ No newline at end of file diff --git a/src/main/java/net/pixtaded/crab/server/ServerThread.java b/src/main/java/net/pixtaded/crab/server/ServerThread.java index ee0ce2d..9889949 100644 --- a/src/main/java/net/pixtaded/crab/server/ServerThread.java +++ b/src/main/java/net/pixtaded/crab/server/ServerThread.java @@ -43,11 +43,11 @@ public class ServerThread implements Runnable { String msg = new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim(); String address = socket.getInetAddress().getHostAddress(); - new Thread(new LogDBThread(date, address, msg)).start(); - String s = Sanitizer.sanitizeString(msg, true); String newContent = server.cache.content() + Sanitizer.formatMessage(date.getTime(), address, s); server.cache = new Logs(newContent.getBytes().length, newContent); + + new Thread(new LogDBThread(date, address, msg)).start(); } case LOGS -> { respond(server.cache.content()); } case LOGS_SIZE -> { @@ -75,15 +75,17 @@ public class ServerThread implements Runnable { Date date; String msg; + String address; public LogDBThread(Date date, String address, String msg) { this.date = date; this.msg = msg; + this.address = address; } @Override public void run() { - server.getDb().logMessage(date, socket.getInetAddress().getHostAddress(), msg); + server.getDb().logMessage(date, address, msg); } } } From 74589d48b0bf27766799fe09169fbf158e47bfed Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sat, 8 Feb 2025 20:34:47 +0300 Subject: [PATCH 13/25] Port the client to RACv2 beta (the server is still not functional) --- .../net/pixtaded/crab/client/CrabClient.java | 40 ++++++++++++------- .../java/net/pixtaded/crab/common/PID.java | 5 +-- .../net/pixtaded/crab/server/CrabServer.java | 2 - .../pixtaded/crab/server/ServerThread.java | 20 +++++----- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index 14a60ce..f3d59c4 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -92,18 +92,27 @@ public class CrabClient implements Crab { break; } - if (!message.isEmpty()) sendPacket(MESSAGE, this.nickname + message); + if (!message.isEmpty()) sendMessage(message); } } - private void sendPacket(byte PID, String argument) throws IOException { - connect(); - String formattedMessage = String.valueOf((char) PID) + argument + "\n"; + private void sendPacket(byte PID, String argument, boolean receiveResponse) throws IOException { + if (socket == null || socket.isClosed()) connect(); + String formattedMessage = String.valueOf((char) PID) + argument; out.print(formattedMessage); out.flush(); - receiveResponse(PID); + if (receiveResponse) receiveResponse(PID); + } + + private void printLogs() { + clearScreen(); + System.out.print(Sanitizer.sanitizeString(cache.content(), false)); + } + + private void sendMessage(String msg) throws IOException { + sendPacket(COMMUNICATION, this.nickname + msg, false); closeConnection(); } @@ -112,14 +121,11 @@ public class CrabClient implements Crab { case LOGS_SIZE -> { char[] buffer = new char[10]; int response = in.read(buffer); - lastBufferLength = Integer.parseInt(new String(buffer).trim()); - } case LOGS -> { - if (cache.sizeInBytes() != lastBufferLength) { - byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength); - cache = new Logs(lastBufferLength, new String(bytes, StandardCharsets.UTF_8)); - } - clearScreen(); - System.out.print(Sanitizer.sanitizeString(cache.content(), false)); + String convertedString = new String(buffer).trim(); + if (!convertedString.isEmpty()) lastBufferLength = Integer.parseInt(convertedString); + } case COMMUNICATION -> { + byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength); + cache = new Logs(lastBufferLength, new String(bytes, StandardCharsets.UTF_8)); } default -> { } } @@ -136,8 +142,12 @@ public class CrabClient implements Crab { } private void getLogs() throws IOException { - sendPacket(LOGS_SIZE, ""); - sendPacket(LOGS, ""); + sendPacket(LOGS_SIZE, "", true); + if (this.cache.sizeInBytes() != lastBufferLength) { + sendPacket(COMMUNICATION, "", true); + } + closeConnection(); + printLogs(); } private void clearScreen() { diff --git a/src/main/java/net/pixtaded/crab/common/PID.java b/src/main/java/net/pixtaded/crab/common/PID.java index 5db96f6..7b46ec3 100644 --- a/src/main/java/net/pixtaded/crab/common/PID.java +++ b/src/main/java/net/pixtaded/crab/common/PID.java @@ -1,7 +1,6 @@ package net.pixtaded.crab.common; public class PID { - public static final byte MESSAGE = 0x30; - public static final byte LOGS_SIZE = 0x31; - public static final byte LOGS = 0x32; + public static final byte LOGS_SIZE = 0x00; + public static final byte COMMUNICATION = 0x01; } diff --git a/src/main/java/net/pixtaded/crab/server/CrabServer.java b/src/main/java/net/pixtaded/crab/server/CrabServer.java index 16ec627..d6605b4 100644 --- a/src/main/java/net/pixtaded/crab/server/CrabServer.java +++ b/src/main/java/net/pixtaded/crab/server/CrabServer.java @@ -10,7 +10,6 @@ import java.util.Scanner; public class CrabServer implements Crab { private ServerSocket serverSocket; - private Socket socket; private boolean isStopped = false; private int port; private final Database db; @@ -72,7 +71,6 @@ public class CrabServer implements Crab { public synchronized void stop() { isStopped = true; try { - if (socket != null) socket.close(); if (serverSocket != null) serverSocket.close(); } catch (IOException e) { System.err.println("An error occured while closing the socket: " + e.getMessage()); diff --git a/src/main/java/net/pixtaded/crab/server/ServerThread.java b/src/main/java/net/pixtaded/crab/server/ServerThread.java index 9889949..3db59bf 100644 --- a/src/main/java/net/pixtaded/crab/server/ServerThread.java +++ b/src/main/java/net/pixtaded/crab/server/ServerThread.java @@ -38,19 +38,21 @@ public class ServerThread implements Runnable { return; } switch (PID[0]) { - case MESSAGE -> { - Date date = new Date(); + case COMMUNICATION -> { String msg = new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim(); - String address = socket.getInetAddress().getHostAddress(); + if (!msg.isEmpty()) { + Date date = new Date(); + String address = socket.getInetAddress().getHostAddress(); - String s = Sanitizer.sanitizeString(msg, true); - String newContent = server.cache.content() + Sanitizer.formatMessage(date.getTime(), address, s); - server.cache = new Logs(newContent.getBytes().length, newContent); + String s = Sanitizer.sanitizeString(msg, true); + String newContent = server.cache.content() + Sanitizer.formatMessage(date.getTime(), address, s); + server.cache = new Logs(newContent.getBytes().length, newContent); - new Thread(new LogDBThread(date, address, msg)).start(); - } case LOGS -> { + new Thread(new LogDBThread(date, address, msg)).start(); + } + } /* case LOGS -> { respond(server.cache.content()); - } case LOGS_SIZE -> { + } */ case LOGS_SIZE -> { respond(String.valueOf(server.cache.sizeInBytes())); } default -> { System.out.println("PID not implemented: " + PID[0]); From 30d90f22f2e9fb023161e913cec9185f897bd196 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sat, 8 Feb 2025 20:42:14 +0300 Subject: [PATCH 14/25] Add RACv1.99 protocol specification --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e163d0..a982c61 100644 --- a/README.md +++ b/README.md @@ -37,4 +37,22 @@ Once the server is running, clients can connect to it and send messages accordin ## RAC Protocol -You can see the RAC protocol documentation [here](https://bedohswe.eu.org/text/rac/protocol.md.html). \ No newline at end of file +1. Message Retrieval + + a. The client initiates a message retrieval session by sending the byte 0x00 to the server. + + b. In response, the server transmits the size of the available messages as an ASCII-encoded string. + + c. After receiving the size, the client must send the following byte or close the connection: + + i. Sending 0x01 instructs the server to transmit the messages. + +2. Message Transmission + + a. To send a message, the client issues a request in the following format: + + 0x01 followed immediately by the message content. + +### Additional Notes: + +Although the protocol may appear similar to RACv1, it is important to note that RACv1.99 represents the beta development phase of RACv2. Consequently, significant changes and enhancements are anticipated. The current specification is implemented in lRACd version 1.99.1 and clRAC version 1.99.1. \ No newline at end of file From a4bf914c418b15e575167023d1d8817eeed7c175 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sat, 8 Feb 2025 21:41:03 +0300 Subject: [PATCH 15/25] Server functionality restored --- .../pixtaded/crab/server/ServerThread.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/pixtaded/crab/server/ServerThread.java b/src/main/java/net/pixtaded/crab/server/ServerThread.java index 3db59bf..ef0927c 100644 --- a/src/main/java/net/pixtaded/crab/server/ServerThread.java +++ b/src/main/java/net/pixtaded/crab/server/ServerThread.java @@ -32,7 +32,7 @@ public class ServerThread implements Runnable { @Override public void run() { try { - byte[] PID = input.readNBytes(1); + byte[] PID = readPID(); if (PID.length == 0) { socket.close(); return; @@ -40,20 +40,18 @@ public class ServerThread implements Runnable { switch (PID[0]) { case COMMUNICATION -> { String msg = new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim(); - if (!msg.isEmpty()) { - Date date = new Date(); - String address = socket.getInetAddress().getHostAddress(); + Date date = new Date(); + String address = socket.getInetAddress().getHostAddress(); - String s = Sanitizer.sanitizeString(msg, true); - String newContent = server.cache.content() + Sanitizer.formatMessage(date.getTime(), address, s); - server.cache = new Logs(newContent.getBytes().length, newContent); + String s = Sanitizer.sanitizeString(msg, true); + String newContent = server.cache.content() + Sanitizer.formatMessage(date.getTime(), address, s); + server.cache = new Logs(newContent.getBytes().length, newContent); - new Thread(new LogDBThread(date, address, msg)).start(); - } - } /* case LOGS -> { - respond(server.cache.content()); - } */ case LOGS_SIZE -> { + new Thread(new LogDBThread(date, address, msg)).start(); + } case LOGS_SIZE -> { respond(String.valueOf(server.cache.sizeInBytes())); + readPID(); + sendLogs(); } default -> { System.out.println("PID not implemented: " + PID[0]); } @@ -64,6 +62,14 @@ public class ServerThread implements Runnable { } } + private byte[] readPID() throws IOException { + return input.readNBytes(1); + } + + private void sendLogs() throws IOException { + respond(server.cache.content()); + } + private void respond(byte[] data) throws IOException { socket.getOutputStream().write(data); socket.getOutputStream().flush(); From 6d21c9bccd15c002a8c93c48f4f6ad5a342d7a48 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sat, 8 Feb 2025 21:42:05 +0300 Subject: [PATCH 16/25] Bump version to 2.0.0-SNAPSHOT --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5f67769..fda7859 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -projectVersion=1.0.5 \ No newline at end of file +projectVersion=2.0.0-SNAPSHOT \ No newline at end of file From 389d7c4868f1ae5d0e736649c5406f8efe5d6ecf Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sat, 8 Feb 2025 22:04:01 +0300 Subject: [PATCH 17/25] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a982c61..546adb4 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,12 @@ To get started with CRAB, follow these steps: ``` 2. **Build the project:** - Ensure you have Java Development Kit (JDK) of version 17 or higher installed. You can build the project using Maven: + Ensure you have Java Development Kit (JDK) of version 17 or higher installed. You can build the project using Gradle: ```bash ./gradlew clean build ``` -3. **Run the bundle**: You will have the built .tar and .zip packages in ./build/distributions directory. +3. **Run the bundle**: You will have the built .jar package in ./build/libs directory. ## Usage From 3e6aefd4d2e3cf509f03082095a5fe51830bd413 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 9 Feb 2025 14:19:44 +0300 Subject: [PATCH 18/25] Add RACv1.99.2 protocol specification --- README.md | 16 +++++++++++----- .../java/net/pixtaded/crab/server/Database.java | 9 +-------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 546adb4..6ff82ed 100644 --- a/README.md +++ b/README.md @@ -39,20 +39,26 @@ Once the server is running, clients can connect to it and send messages accordin 1. Message Retrieval - a. The client initiates a message retrieval session by sending the byte 0x00 to the server. + a. The client initiates a message retrieval session by sending the byte `0x00` to the server. b. In response, the server transmits the size of the available messages as an ASCII-encoded string. - c. After receiving the size, the client must send the following byte or close the connection: + c. After receiving the size, the client must send one of the following bytes or close the connection: - i. Sending 0x01 instructs the server to transmit the messages. + i. Sending `0x01` instructs the server to transmit all messages in full. + + ii. Sending `0x02` followed by the client’s cached messages length (as an ASCII string, e.g., `0x02"1024"`) instructs the server to transmit only new messages added since the cached length. The server sends messages starting from the cached length offset, and the client updates its cached length to the total size received in step 1b after processing the new messages. 2. Message Transmission a. To send a message, the client issues a request in the following format: - 0x01 followed immediately by the message content. + `0x01` followed immediately by the message content. ### Additional Notes: -Although the protocol may appear similar to RACv1, it is important to note that RACv1.99 represents the beta development phase of RACv2. Consequently, significant changes and enhancements are anticipated. The current specification is implemented in lRACd version 1.99.1 and clRAC version 1.99.1. \ No newline at end of file +- Although the protocol may appear similar to RACv1, it is important to note that RACv1.99 represents the beta development phase of RACv2. Consequently, significant changes and enhancements are anticipated. The current specification is implemented in `lRACd` version 1.99.2 and `clRAC` version 1.99.2. + +- When using `0x02` for incremental retrieval, the client must ensure the cached length is synchronized with the server’s total message length (retrieved via `0x00`). The server sends messages from the cached length onward, and the client calculates the read size as `(total_length - cached_length)`. + +- After receiving incremental messages, the client must update its cached length to the total length provided in step 1b to maintain consistency in subsequent requests. \ No newline at end of file diff --git a/src/main/java/net/pixtaded/crab/server/Database.java b/src/main/java/net/pixtaded/crab/server/Database.java index b88c13f..abe6ea5 100644 --- a/src/main/java/net/pixtaded/crab/server/Database.java +++ b/src/main/java/net/pixtaded/crab/server/Database.java @@ -6,7 +6,7 @@ import net.pixtaded.crab.common.Sanitizer; import java.sql.*; import java.util.Date; -public class Database implements AutoCloseable { +public class Database { private Connection connection; @@ -61,11 +61,4 @@ public class Database implements AutoCloseable { String logsString = s.toString(); return new Logs(logsString.isEmpty() ? 0 : logsString.getBytes().length, logsString); } - - @Override - public void close() throws SQLException { - if (connection != null && !connection.isClosed()) { - connection.close(); - } - } } From 61ddfc86d17b3a576d30f731556dd9fa62f7899a Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 9 Feb 2025 14:47:01 +0300 Subject: [PATCH 19/25] Port the client to RACv1.99.2 --- src/main/java/net/pixtaded/crab/client/CrabClient.java | 8 ++++---- src/main/java/net/pixtaded/crab/common/PID.java | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index f3d59c4..b00e932 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -123,9 +123,9 @@ public class CrabClient implements Crab { int response = in.read(buffer); String convertedString = new String(buffer).trim(); if (!convertedString.isEmpty()) lastBufferLength = Integer.parseInt(convertedString); - } case COMMUNICATION -> { - byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength); - cache = new Logs(lastBufferLength, new String(bytes, StandardCharsets.UTF_8)); + } case LOGS -> { + byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength - cache.sizeInBytes()); + cache = new Logs(lastBufferLength, cache.content() + new String(bytes, StandardCharsets.UTF_8)); } default -> { } } @@ -144,7 +144,7 @@ public class CrabClient implements Crab { private void getLogs() throws IOException { sendPacket(LOGS_SIZE, "", true); if (this.cache.sizeInBytes() != lastBufferLength) { - sendPacket(COMMUNICATION, "", true); + sendPacket(LOGS, String.valueOf(cache.sizeInBytes()), true); } closeConnection(); printLogs(); diff --git a/src/main/java/net/pixtaded/crab/common/PID.java b/src/main/java/net/pixtaded/crab/common/PID.java index 7b46ec3..d169692 100644 --- a/src/main/java/net/pixtaded/crab/common/PID.java +++ b/src/main/java/net/pixtaded/crab/common/PID.java @@ -3,4 +3,5 @@ package net.pixtaded.crab.common; public class PID { public static final byte LOGS_SIZE = 0x00; public static final byte COMMUNICATION = 0x01; + public static final byte LOGS = 0x02; } From 57d5f5ea1ec5c04730b9b4864fbbde1927453cef Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 9 Feb 2025 15:32:22 +0300 Subject: [PATCH 20/25] Port the server to RACv1.99.2 and handle logs erasing in client --- .../net/pixtaded/crab/client/CrabClient.java | 12 ++++--- .../java/net/pixtaded/crab/common/Util.java | 12 +++++++ .../pixtaded/crab/server/ServerThread.java | 35 ++++++++++++------- 3 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 src/main/java/net/pixtaded/crab/common/Util.java diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index b00e932..00491b9 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -3,6 +3,7 @@ package net.pixtaded.crab.client; import net.pixtaded.crab.common.Crab; import net.pixtaded.crab.common.Logs; import net.pixtaded.crab.common.Sanitizer; +import net.pixtaded.crab.common.Util; import java.io.*; import java.net.InetSocketAddress; @@ -119,13 +120,14 @@ public class CrabClient implements Crab { private void receiveResponse(byte PID) throws IOException { switch (PID) { case LOGS_SIZE -> { - char[] buffer = new char[10]; - int response = in.read(buffer); - String convertedString = new String(buffer).trim(); + String convertedString = Util.readAsciiNumber(in); if (!convertedString.isEmpty()) lastBufferLength = Integer.parseInt(convertedString); } case LOGS -> { byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength - cache.sizeInBytes()); cache = new Logs(lastBufferLength, cache.content() + new String(bytes, StandardCharsets.UTF_8)); + } case COMMUNICATION -> { + byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength); + cache = new Logs(lastBufferLength, new String(bytes, StandardCharsets.UTF_8)); } default -> { } } @@ -143,8 +145,10 @@ public class CrabClient implements Crab { private void getLogs() throws IOException { sendPacket(LOGS_SIZE, "", true); - if (this.cache.sizeInBytes() != lastBufferLength) { + if (this.cache.sizeInBytes() < lastBufferLength) { sendPacket(LOGS, String.valueOf(cache.sizeInBytes()), true); + } else if (this.cache.sizeInBytes() != lastBufferLength) { + sendPacket(COMMUNICATION, "", true); } closeConnection(); printLogs(); diff --git a/src/main/java/net/pixtaded/crab/common/Util.java b/src/main/java/net/pixtaded/crab/common/Util.java new file mode 100644 index 0000000..52583d8 --- /dev/null +++ b/src/main/java/net/pixtaded/crab/common/Util.java @@ -0,0 +1,12 @@ +package net.pixtaded.crab.common; + +import java.io.BufferedReader; +import java.io.IOException; + +public class Util { + public static String readAsciiNumber(BufferedReader in) throws IOException { + char[] buffer = new char[10]; + int response = in.read(buffer); + return new String(buffer).trim(); + } +} diff --git a/src/main/java/net/pixtaded/crab/server/ServerThread.java b/src/main/java/net/pixtaded/crab/server/ServerThread.java index ef0927c..40df329 100644 --- a/src/main/java/net/pixtaded/crab/server/ServerThread.java +++ b/src/main/java/net/pixtaded/crab/server/ServerThread.java @@ -2,6 +2,7 @@ package net.pixtaded.crab.server; import net.pixtaded.crab.common.Logs; import net.pixtaded.crab.common.Sanitizer; +import net.pixtaded.crab.common.Util; import java.io.*; import java.net.Socket; @@ -12,13 +13,12 @@ import static net.pixtaded.crab.common.PID.*; public class ServerThread implements Runnable { - private Socket socket; + private final Socket socket; private PrintWriter out; - private BufferedReader in; + private final BufferedReader in; private OutputStream output; - private InputStream input; - private byte PID; - private CrabServer server; + private final InputStream input; + private final CrabServer server; public ServerThread(Socket socket, CrabServer server) throws IOException { this.socket = socket; @@ -50,11 +50,13 @@ public class ServerThread implements Runnable { new Thread(new LogDBThread(date, address, msg)).start(); } case LOGS_SIZE -> { respond(String.valueOf(server.cache.sizeInBytes())); - readPID(); - sendLogs(); - } default -> { - System.out.println("PID not implemented: " + PID[0]); - } + byte[] logPID = readPID(); + if (logPID.length == 0) { + socket.close(); + return; + } + sendLogs(logPID[0]); + } default -> System.out.println("PID not implemented: " + PID[0]); } socket.close(); } catch (Exception e) { @@ -66,8 +68,17 @@ public class ServerThread implements Runnable { return input.readNBytes(1); } - private void sendLogs() throws IOException { - respond(server.cache.content()); + private void sendLogs(byte PID) throws IOException { + if (PID == COMMUNICATION) respond(server.cache.content()); + if (PID == LOGS) { + String clientSize = Util.readAsciiNumber(in); + int clientSizeNum = Integer.parseInt(clientSize); + byte[] serverLogs = server.cache.content().getBytes(StandardCharsets.UTF_8); + int logPartSize = serverLogs.length - clientSizeNum; + byte[] logPart = new byte[logPartSize]; + System.arraycopy(serverLogs, serverLogs.length - logPartSize, logPart, 0, logPartSize); + respond(logPart); + } } private void respond(byte[] data) throws IOException { From 72074ca1175a8386b91d1939c48fcc76897d92ac Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 9 Feb 2025 19:01:10 +0300 Subject: [PATCH 21/25] Add client-specific colors (No Mefedroniy support 'cause no parsing :( ) --- .../net/pixtaded/crab/client/ClientColor.java | 4 +++ .../net/pixtaded/crab/client/ClientUtil.java | 32 +++++++++++++++++++ .../net/pixtaded/crab/client/CrabClient.java | 6 ++-- 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/main/java/net/pixtaded/crab/client/ClientColor.java create mode 100644 src/main/java/net/pixtaded/crab/client/ClientUtil.java diff --git a/src/main/java/net/pixtaded/crab/client/ClientColor.java b/src/main/java/net/pixtaded/crab/client/ClientColor.java new file mode 100644 index 0000000..7a2bbf3 --- /dev/null +++ b/src/main/java/net/pixtaded/crab/client/ClientColor.java @@ -0,0 +1,4 @@ +package net.pixtaded.crab.client; + +public record ClientColor(String regex, String color) { +} diff --git a/src/main/java/net/pixtaded/crab/client/ClientUtil.java b/src/main/java/net/pixtaded/crab/client/ClientUtil.java new file mode 100644 index 0000000..48568c5 --- /dev/null +++ b/src/main/java/net/pixtaded/crab/client/ClientUtil.java @@ -0,0 +1,32 @@ +package net.pixtaded.crab.client; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ClientUtil { + + public static final String COLOR_KEY = "\u2550\u2550\u2550"; + + public static final ClientColor[] colors = { + new ClientColor( COLOR_KEY + "(<.*?>)", "\033[0;31m$1\033[0m"), + new ClientColor("\uB9AC\u3E70(<.*?>)", "\033[0;32m$1\033[0m"), + new ClientColor(" (<.*?>)", " \033[0;34m$1\033[0m") + /* + I'm too lazy to add a message parser, which is absolutely required for Mefedroniy color support. For now, it will be just white. + Also, there is another problem with that - there is no "server log format" in the protocol specification, + and I want my client to be as compatible as possible. + */ + }; + + public static String clientColors(String s) { + for (ClientColor color : colors) s = matchClientKey(s, color); + return s; + } + + private static String matchClientKey(String s, ClientColor color) { + Pattern p = Pattern.compile(color.regex()); + Matcher m = p.matcher(s); + + return m.replaceAll(color.color()); + } +} diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index 00491b9..8a58ca8 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -30,7 +30,7 @@ public class CrabClient implements Crab { this.serverAddress = serverAddress; this.port = port; if (nickname != null) - this.nickname = "<" + nickname + "> "; + this.nickname = ClientUtil.COLOR_KEY + "<" + nickname + "> "; else this.nickname = ""; } @@ -71,7 +71,7 @@ public class CrabClient implements Crab { System.out.print("Enter your nickname (leave empty for no nickname): "); nickname = scanner.nextLine(); if (!nickname.isEmpty()) - nickname = "<" + nickname + "> "; + nickname = ClientUtil.COLOR_KEY + "<" + nickname + "> "; } private void connect() throws IOException { @@ -109,7 +109,7 @@ public class CrabClient implements Crab { private void printLogs() { clearScreen(); - System.out.print(Sanitizer.sanitizeString(cache.content(), false)); + System.out.print(ClientUtil.clientColors(Sanitizer.sanitizeString(cache.content(), false))); } private void sendMessage(String msg) throws IOException { From f03666beadefc293f6636266162c7687246ca0b2 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Sun, 9 Feb 2025 20:15:17 +0300 Subject: [PATCH 22/25] AntiClear (protection against /clear in bRAC) --- src/main/java/net/pixtaded/crab/common/Sanitizer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/pixtaded/crab/common/Sanitizer.java b/src/main/java/net/pixtaded/crab/common/Sanitizer.java index 2a38145..ba893a2 100644 --- a/src/main/java/net/pixtaded/crab/common/Sanitizer.java +++ b/src/main/java/net/pixtaded/crab/common/Sanitizer.java @@ -6,6 +6,8 @@ public class Sanitizer { if (sanitizeNewlines) { sanitized = sanitized.replaceAll("\n", "\\\\n"); if (!s.endsWith("\n")) sanitized += '\n'; + } else { + sanitized = sanitized.replaceAll("\n\n+", "\n"); } return sanitized; } @@ -13,4 +15,4 @@ public class Sanitizer { public static String formatMessage(long timeMillis, String address, String content) { return String.format("[%td.%1$tm.%1$tY %1$tR] {%s} %s", timeMillis, address, content); } -} +} \ No newline at end of file From 933af5c5e02264bd6422df60ebf9ecb7a327269c Mon Sep 17 00:00:00 2001 From: pixtaded Date: Tue, 11 Feb 2025 15:06:34 +0300 Subject: [PATCH 23/25] Mefedroniy color support --- src/main/java/net/pixtaded/crab/client/ClientUtil.java | 8 ++------ src/main/java/net/pixtaded/crab/client/CrabClient.java | 2 +- src/main/java/net/pixtaded/crab/server/ServerThread.java | 5 +++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/pixtaded/crab/client/ClientUtil.java b/src/main/java/net/pixtaded/crab/client/ClientUtil.java index 48568c5..66e605f 100644 --- a/src/main/java/net/pixtaded/crab/client/ClientUtil.java +++ b/src/main/java/net/pixtaded/crab/client/ClientUtil.java @@ -10,12 +10,8 @@ public class ClientUtil { public static final ClientColor[] colors = { new ClientColor( COLOR_KEY + "(<.*?>)", "\033[0;31m$1\033[0m"), new ClientColor("\uB9AC\u3E70(<.*?>)", "\033[0;32m$1\033[0m"), - new ClientColor(" (<.*?>)", " \033[0;34m$1\033[0m") - /* - I'm too lazy to add a message parser, which is absolutely required for Mefedroniy color support. For now, it will be just white. - Also, there is another problem with that - there is no "server log format" in the protocol specification, - and I want my client to be as compatible as possible. - */ + new ClientColor(" (<.*?>)", " \033[0;34m$1\033[0m"), + new ClientColor("\u00B0\u0298(<.*?>)", "\033[0;35m$1\033[0m") }; public static String clientColors(String s) { diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index 8a58ca8..918258d 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -158,4 +158,4 @@ public class CrabClient implements Crab { System.out.print("\033[999999S\033[H\033[2J"); } -} +} \ No newline at end of file diff --git a/src/main/java/net/pixtaded/crab/server/ServerThread.java b/src/main/java/net/pixtaded/crab/server/ServerThread.java index 40df329..8be9e9e 100644 --- a/src/main/java/net/pixtaded/crab/server/ServerThread.java +++ b/src/main/java/net/pixtaded/crab/server/ServerThread.java @@ -69,8 +69,9 @@ public class ServerThread implements Runnable { } private void sendLogs(byte PID) throws IOException { - if (PID == COMMUNICATION) respond(server.cache.content()); - if (PID == LOGS) { + if (PID == COMMUNICATION) { + respond(server.cache.content()); + } else if (PID == LOGS) { String clientSize = Util.readAsciiNumber(in); int clientSizeNum = Integer.parseInt(clientSize); byte[] serverLogs = server.cache.content().getBytes(StandardCharsets.UTF_8); From 12e595bb7323c820016a01c9c582a70f81ae77f3 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Tue, 11 Feb 2025 20:38:58 +0300 Subject: [PATCH 24/25] prepare for racv2 final update (I hate PID collisions) --- .../java/net/pixtaded/crab/client/CrabClient.java | 12 ++++++------ src/main/java/net/pixtaded/crab/common/PID.java | 8 +++++--- .../java/net/pixtaded/crab/server/ServerThread.java | 10 +++++----- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/pixtaded/crab/client/CrabClient.java b/src/main/java/net/pixtaded/crab/client/CrabClient.java index 918258d..516dfe4 100644 --- a/src/main/java/net/pixtaded/crab/client/CrabClient.java +++ b/src/main/java/net/pixtaded/crab/client/CrabClient.java @@ -99,7 +99,7 @@ public class CrabClient implements Crab { private void sendPacket(byte PID, String argument, boolean receiveResponse) throws IOException { if (socket == null || socket.isClosed()) connect(); - String formattedMessage = String.valueOf((char) PID) + argument; + String formattedMessage = (char) PID + argument; out.print(formattedMessage); out.flush(); @@ -113,7 +113,7 @@ public class CrabClient implements Crab { } private void sendMessage(String msg) throws IOException { - sendPacket(COMMUNICATION, this.nickname + msg, false); + sendPacket(MESSAGE, this.nickname + msg, false); closeConnection(); } @@ -122,10 +122,10 @@ public class CrabClient implements Crab { case LOGS_SIZE -> { String convertedString = Util.readAsciiNumber(in); if (!convertedString.isEmpty()) lastBufferLength = Integer.parseInt(convertedString); - } case LOGS -> { + } case CACHED_LOGS -> { byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength - cache.sizeInBytes()); cache = new Logs(lastBufferLength, cache.content() + new String(bytes, StandardCharsets.UTF_8)); - } case COMMUNICATION -> { + } case LOGS -> { byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength); cache = new Logs(lastBufferLength, new String(bytes, StandardCharsets.UTF_8)); } default -> { @@ -146,9 +146,9 @@ public class CrabClient implements Crab { private void getLogs() throws IOException { sendPacket(LOGS_SIZE, "", true); if (this.cache.sizeInBytes() < lastBufferLength) { - sendPacket(LOGS, String.valueOf(cache.sizeInBytes()), true); + sendPacket(CACHED_LOGS, String.valueOf(cache.sizeInBytes()), true); } else if (this.cache.sizeInBytes() != lastBufferLength) { - sendPacket(COMMUNICATION, "", true); + sendPacket(LOGS, "", true); } closeConnection(); printLogs(); diff --git a/src/main/java/net/pixtaded/crab/common/PID.java b/src/main/java/net/pixtaded/crab/common/PID.java index d169692..59a91e1 100644 --- a/src/main/java/net/pixtaded/crab/common/PID.java +++ b/src/main/java/net/pixtaded/crab/common/PID.java @@ -2,6 +2,8 @@ package net.pixtaded.crab.common; public class PID { public static final byte LOGS_SIZE = 0x00; - public static final byte COMMUNICATION = 0x01; - public static final byte LOGS = 0x02; -} + public static final byte LOGS = 0x01; + public static final byte MESSAGE = 0x01; + public static final byte CACHED_LOGS = 0x02; + public static final byte AUTHENTICATED_MESSAGE = 0x02; +} \ No newline at end of file diff --git a/src/main/java/net/pixtaded/crab/server/ServerThread.java b/src/main/java/net/pixtaded/crab/server/ServerThread.java index 8be9e9e..ae4c537 100644 --- a/src/main/java/net/pixtaded/crab/server/ServerThread.java +++ b/src/main/java/net/pixtaded/crab/server/ServerThread.java @@ -14,9 +14,9 @@ import static net.pixtaded.crab.common.PID.*; public class ServerThread implements Runnable { private final Socket socket; - private PrintWriter out; + private final PrintWriter out; private final BufferedReader in; - private OutputStream output; + private final OutputStream output; private final InputStream input; private final CrabServer server; @@ -38,7 +38,7 @@ public class ServerThread implements Runnable { return; } switch (PID[0]) { - case COMMUNICATION -> { + case MESSAGE -> { String msg = new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim(); Date date = new Date(); String address = socket.getInetAddress().getHostAddress(); @@ -69,9 +69,9 @@ public class ServerThread implements Runnable { } private void sendLogs(byte PID) throws IOException { - if (PID == COMMUNICATION) { + if (PID == LOGS) { respond(server.cache.content()); - } else if (PID == LOGS) { + } else if (PID == CACHED_LOGS) { String clientSize = Util.readAsciiNumber(in); int clientSizeNum = Integer.parseInt(clientSize); byte[] serverLogs = server.cache.content().getBytes(StandardCharsets.UTF_8); From b8d4ab90f2b1b46fe868a5f0610d4548fa0a8eb4 Mon Sep 17 00:00:00 2001 From: pixtaded Date: Tue, 11 Feb 2025 21:04:18 +0300 Subject: [PATCH 25/25] Add RACv2 protocol specification. (I hate it) --- README.md | 26 ++++++++++++++++--- .../java/net/pixtaded/crab/common/PID.java | 1 + 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6ff82ed..aa58369 100644 --- a/README.md +++ b/README.md @@ -51,14 +51,32 @@ Once the server is running, clients can connect to it and send messages accordin 2. Message Transmission - a. To send a message, the client issues a request in the following format: + a. To send a message, the client issues a request in one of the following formats: - `0x01` followed immediately by the message content. + i. Unauthenticated Message: The client sends the byte `0x01` followed immediately by the message content. The server does not send a response. + + ii. Authenticated Message: The client sends the byte `0x02` followed by the username, a newline character (`\n`), the password, a newline character and the message content. The server responds with a single byte: + - `0x01` indicates the user does not exist. + - `0x02` indicates the password is incorrect. + - A successful authentication results in the server accepting the message without sending a response. + +3. User Registration + + a. To register a new user, the client sends a request formatted as: + - The byte `0x03`. + - The username, followed by a newline character (`\n`). + - The password. + + b. The server processes the request and responds with a single byte: + - `0x01` if the username already exists. + - A successful registration is assumed if no error byte (`0x01`) is received. The client should close the connection after handling the response. ### Additional Notes: -- Although the protocol may appear similar to RACv1, it is important to note that RACv1.99 represents the beta development phase of RACv2. Consequently, significant changes and enhancements are anticipated. The current specification is implemented in `lRACd` version 1.99.2 and `clRAC` version 1.99.2. +- The current specification of RACv2 is implemented in `lRACd` version 2.0.0 and `clRAC` version 2.0.0. - When using `0x02` for incremental retrieval, the client must ensure the cached length is synchronized with the server’s total message length (retrieved via `0x00`). The server sends messages from the cached length onward, and the client calculates the read size as `(total_length - cached_length)`. -- After receiving incremental messages, the client must update its cached length to the total length provided in step 1b to maintain consistency in subsequent requests. \ No newline at end of file +- After receiving incremental messages, the client must update its cached length to the total length provided in step 1b to maintain consistency in subsequent requests. + +- For authenticated message transmission (`0x02`) or user registration (`0x03`), the client must follow the specified format precisely. The server validates the structure of the request and responds with error codes only for specific failure conditions (e.g., invalid credentials or duplicate usernames). \ No newline at end of file diff --git a/src/main/java/net/pixtaded/crab/common/PID.java b/src/main/java/net/pixtaded/crab/common/PID.java index 59a91e1..db9292e 100644 --- a/src/main/java/net/pixtaded/crab/common/PID.java +++ b/src/main/java/net/pixtaded/crab/common/PID.java @@ -6,4 +6,5 @@ public class PID { public static final byte MESSAGE = 0x01; public static final byte CACHED_LOGS = 0x02; public static final byte AUTHENTICATED_MESSAGE = 0x02; + public static final byte REGISTER = 0x03; } \ No newline at end of file