Compare commits
No commits in common. "master" and "2.0.0-snapshot-202502091532" have entirely different histories.
master
...
2.0.0-snap
24
README.md
24
README.md
@ -51,32 +51,14 @@ Once the server is running, clients can connect to it and send messages accordin
|
|||||||
|
|
||||||
2. Message Transmission
|
2. Message Transmission
|
||||||
|
|
||||||
a. To send a message, the client issues a request in one of the following formats:
|
a. To send a message, the client issues a request in the following format:
|
||||||
|
|
||||||
i. Unauthenticated Message: The client sends the byte `0x01` followed immediately by the message content. The server does not send a response.
|
`0x01` followed immediately by the message content.
|
||||||
|
|
||||||
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:
|
### Additional Notes:
|
||||||
|
|
||||||
- The current specification of RACv2 is implemented in `lRACd` version 2.0.0 and `clRAC` version 2.0.0.
|
- 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)`.
|
- 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.
|
- 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).
|
|
@ -17,7 +17,7 @@ public class Main {
|
|||||||
case "help" -> {
|
case "help" -> {
|
||||||
System.out.println("crab help - print this message.");
|
System.out.println("crab help - print this message.");
|
||||||
System.out.println("crab client <ip> <port> [nick] - connect to a server.");
|
System.out.println("crab client <ip> <port> [nick] - connect to a server.");
|
||||||
System.out.println("crab server <port> [PROXY protocol off/on] - start a server.");
|
System.out.println("crab server <port> - start a server.");
|
||||||
}
|
}
|
||||||
case "client" -> {
|
case "client" -> {
|
||||||
CrabClient client;
|
CrabClient client;
|
||||||
@ -31,23 +31,18 @@ public class Main {
|
|||||||
}
|
}
|
||||||
case "server" -> {
|
case "server" -> {
|
||||||
CrabServer server;
|
CrabServer server;
|
||||||
if (args.length > 1) {
|
try {
|
||||||
boolean isProxied = false;
|
server = new CrabServer(Integer.parseInt(args[1]));
|
||||||
if (args.length > 2)
|
} catch (NumberFormatException e) {
|
||||||
isProxied = args[2].equals("on");
|
System.err.println("Port is not a number.");
|
||||||
try {
|
|
||||||
server = new CrabServer(Integer.parseInt(args[1]), isProxied);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
System.err.println("Port is not a number.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.err.println("Not enough arguments.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
server.run();
|
server.run();
|
||||||
}
|
}
|
||||||
default -> System.err.println("Unknown argument");
|
default -> {
|
||||||
|
System.err.println("Unknown argument");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
package net.pixtaded.crab.client;
|
|
||||||
|
|
||||||
public record ClientColor(String regex, String color) {
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
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"),
|
|
||||||
new ClientColor("\u00B0\u0298(<.*?>)", "\033[0;35m$1\033[0m")
|
|
||||||
};
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,7 +30,7 @@ public class CrabClient implements Crab {
|
|||||||
this.serverAddress = serverAddress;
|
this.serverAddress = serverAddress;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
if (nickname != null)
|
if (nickname != null)
|
||||||
this.nickname = ClientUtil.COLOR_KEY + "<" + nickname + "> ";
|
this.nickname = "<" + nickname + "> ";
|
||||||
else
|
else
|
||||||
this.nickname = "";
|
this.nickname = "";
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ public class CrabClient implements Crab {
|
|||||||
System.out.print("Enter your nickname (leave empty for no nickname): ");
|
System.out.print("Enter your nickname (leave empty for no nickname): ");
|
||||||
nickname = scanner.nextLine();
|
nickname = scanner.nextLine();
|
||||||
if (!nickname.isEmpty())
|
if (!nickname.isEmpty())
|
||||||
nickname = ClientUtil.COLOR_KEY + "<" + nickname + "> ";
|
nickname = "<" + nickname + "> ";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connect() throws IOException {
|
private void connect() throws IOException {
|
||||||
@ -99,7 +99,7 @@ public class CrabClient implements Crab {
|
|||||||
|
|
||||||
private void sendPacket(byte PID, String argument, boolean receiveResponse) throws IOException {
|
private void sendPacket(byte PID, String argument, boolean receiveResponse) throws IOException {
|
||||||
if (socket == null || socket.isClosed()) connect();
|
if (socket == null || socket.isClosed()) connect();
|
||||||
String formattedMessage = (char) PID + argument;
|
String formattedMessage = String.valueOf((char) PID) + argument;
|
||||||
|
|
||||||
out.print(formattedMessage);
|
out.print(formattedMessage);
|
||||||
out.flush();
|
out.flush();
|
||||||
@ -109,11 +109,11 @@ public class CrabClient implements Crab {
|
|||||||
|
|
||||||
private void printLogs() {
|
private void printLogs() {
|
||||||
clearScreen();
|
clearScreen();
|
||||||
System.out.print(ClientUtil.clientColors(Sanitizer.sanitizeString(cache.content(), false)));
|
System.out.print(Sanitizer.sanitizeString(cache.content(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(String msg) throws IOException {
|
private void sendMessage(String msg) throws IOException {
|
||||||
sendPacket(MESSAGE, this.nickname + msg, false);
|
sendPacket(COMMUNICATION, this.nickname + msg, false);
|
||||||
closeConnection();
|
closeConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,10 +122,10 @@ public class CrabClient implements Crab {
|
|||||||
case LOGS_SIZE -> {
|
case LOGS_SIZE -> {
|
||||||
String convertedString = Util.readAsciiNumber(in);
|
String convertedString = Util.readAsciiNumber(in);
|
||||||
if (!convertedString.isEmpty()) lastBufferLength = Integer.parseInt(convertedString);
|
if (!convertedString.isEmpty()) lastBufferLength = Integer.parseInt(convertedString);
|
||||||
} case CACHED_LOGS -> {
|
} case LOGS -> {
|
||||||
byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength - cache.sizeInBytes());
|
byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength - cache.sizeInBytes());
|
||||||
cache = new Logs(lastBufferLength, cache.content() + new String(bytes, StandardCharsets.UTF_8));
|
cache = new Logs(lastBufferLength, cache.content() + new String(bytes, StandardCharsets.UTF_8));
|
||||||
} case LOGS -> {
|
} case COMMUNICATION -> {
|
||||||
byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength);
|
byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength);
|
||||||
cache = new Logs(lastBufferLength, new String(bytes, StandardCharsets.UTF_8));
|
cache = new Logs(lastBufferLength, new String(bytes, StandardCharsets.UTF_8));
|
||||||
} default -> {
|
} default -> {
|
||||||
@ -146,9 +146,9 @@ public class CrabClient implements Crab {
|
|||||||
private void getLogs() throws IOException {
|
private void getLogs() throws IOException {
|
||||||
sendPacket(LOGS_SIZE, "", true);
|
sendPacket(LOGS_SIZE, "", true);
|
||||||
if (this.cache.sizeInBytes() < lastBufferLength) {
|
if (this.cache.sizeInBytes() < lastBufferLength) {
|
||||||
sendPacket(CACHED_LOGS, String.valueOf(cache.sizeInBytes()), true);
|
sendPacket(LOGS, String.valueOf(cache.sizeInBytes()), true);
|
||||||
} else if (this.cache.sizeInBytes() != lastBufferLength) {
|
} else if (this.cache.sizeInBytes() != lastBufferLength) {
|
||||||
sendPacket(LOGS, "", true);
|
sendPacket(COMMUNICATION, "", true);
|
||||||
}
|
}
|
||||||
closeConnection();
|
closeConnection();
|
||||||
printLogs();
|
printLogs();
|
||||||
|
@ -2,9 +2,6 @@ package net.pixtaded.crab.common;
|
|||||||
|
|
||||||
public class PID {
|
public class PID {
|
||||||
public static final byte LOGS_SIZE = 0x00;
|
public static final byte LOGS_SIZE = 0x00;
|
||||||
public static final byte LOGS = 0x01;
|
public static final byte COMMUNICATION = 0x01;
|
||||||
public static final byte MESSAGE = 0x01;
|
public static final byte LOGS = 0x02;
|
||||||
public static final byte CACHED_LOGS = 0x02;
|
|
||||||
public static final byte AUTHENTICATED_MESSAGE = 0x02;
|
|
||||||
public static final byte REGISTER = 0x03;
|
|
||||||
}
|
}
|
@ -6,8 +6,6 @@ public class Sanitizer {
|
|||||||
if (sanitizeNewlines) {
|
if (sanitizeNewlines) {
|
||||||
sanitized = sanitized.replaceAll("\n", "\\\\n");
|
sanitized = sanitized.replaceAll("\n", "\\\\n");
|
||||||
if (!s.endsWith("\n")) sanitized += '\n';
|
if (!s.endsWith("\n")) sanitized += '\n';
|
||||||
} else {
|
|
||||||
sanitized = sanitized.replaceAll("\n\n+", "\n");
|
|
||||||
}
|
}
|
||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,14 @@ import net.pixtaded.crab.common.Crab;
|
|||||||
import net.pixtaded.crab.common.Logs;
|
import net.pixtaded.crab.common.Logs;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
public class CrabServer implements Crab {
|
public class CrabServer implements Crab {
|
||||||
|
|
||||||
private ServerSocket serverSocket;
|
private ServerSocket serverSocket;
|
||||||
private boolean isStopped = false;
|
private boolean isStopped = false;
|
||||||
private boolean isProxied = false;
|
|
||||||
private int port;
|
private int port;
|
||||||
private final Database db;
|
private final Database db;
|
||||||
public Logs cache = new Logs(0, "");
|
public Logs cache = new Logs(0, "");
|
||||||
@ -22,10 +19,9 @@ public class CrabServer implements Crab {
|
|||||||
this.db = new Database("data.db");
|
this.db = new Database("data.db");
|
||||||
}
|
}
|
||||||
|
|
||||||
public CrabServer(int port, boolean isProxied) {
|
public CrabServer(int port) {
|
||||||
this.db = new Database("data.db");
|
this.db = new Database("data.db");
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.isProxied = isProxied;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -57,28 +53,11 @@ public class CrabServer implements Crab {
|
|||||||
System.out.println("Enter a correct port number: ");
|
System.out.println("Enter a correct port number: ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.print("Enable PROXY protocol? (on/off): ");
|
|
||||||
while (true) {
|
|
||||||
String s = scanner.nextLine();
|
|
||||||
if (s.equals("on")) {
|
|
||||||
this.isProxied = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (s.equals("off")) {
|
|
||||||
this.isProxied = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
System.out.println("Enter either \"on\" or \"off\".");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void listen() throws IOException {
|
private void listen() throws IOException {
|
||||||
Scanner scanner = new Scanner(System.in);
|
Scanner scanner = new Scanner(System.in);
|
||||||
if (this.isProxied) {
|
serverSocket = new ServerSocket(port);
|
||||||
serverSocket = new ServerSocket(port, 0, InetAddress.getLoopbackAddress());
|
|
||||||
} else {
|
|
||||||
serverSocket = new ServerSocket(port);
|
|
||||||
}
|
|
||||||
System.out.printf("Server successfully started! Listening on port %s.\nTo stop the server, type 'q'.\n", port);
|
System.out.printf("Server successfully started! Listening on port %s.\nTo stop the server, type 'q'.\n", port);
|
||||||
ServerCLI cli = new ServerCLI(scanner, this);
|
ServerCLI cli = new ServerCLI(scanner, this);
|
||||||
new Thread(cli).start();
|
new Thread(cli).start();
|
||||||
@ -93,11 +72,8 @@ public class CrabServer implements Crab {
|
|||||||
isStopped = true;
|
isStopped = true;
|
||||||
try {
|
try {
|
||||||
if (serverSocket != null) serverSocket.close();
|
if (serverSocket != null) serverSocket.close();
|
||||||
getDb().close();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println("An error occured while closing the socket: " + e.getMessage());
|
System.err.println("An error occured while closing the socket: " + e.getMessage());
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} finally {
|
} finally {
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
@ -106,8 +82,4 @@ public class CrabServer implements Crab {
|
|||||||
public Database getDb() {
|
public Database getDb() {
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isProxied() {
|
|
||||||
return this.isProxied;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,4 @@ public class Database {
|
|||||||
String logsString = s.toString();
|
String logsString = s.toString();
|
||||||
return new Logs(logsString.isEmpty() ? 0 : logsString.getBytes().length, logsString);
|
return new Logs(logsString.isEmpty() ? 0 : logsString.getBytes().length, logsString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws SQLException {
|
|
||||||
connection.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import net.pixtaded.crab.common.Util;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import static net.pixtaded.crab.common.PID.*;
|
import static net.pixtaded.crab.common.PID.*;
|
||||||
@ -15,9 +14,9 @@ import static net.pixtaded.crab.common.PID.*;
|
|||||||
public class ServerThread implements Runnable {
|
public class ServerThread implements Runnable {
|
||||||
|
|
||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
private final PrintWriter out;
|
private PrintWriter out;
|
||||||
private final BufferedReader in;
|
private final BufferedReader in;
|
||||||
private final OutputStream output;
|
private OutputStream output;
|
||||||
private final InputStream input;
|
private final InputStream input;
|
||||||
private final CrabServer server;
|
private final CrabServer server;
|
||||||
|
|
||||||
@ -38,40 +37,11 @@ public class ServerThread implements Runnable {
|
|||||||
socket.close();
|
socket.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String address = socket.getInetAddress().getHostAddress();
|
|
||||||
if (PID[0] == 'P') {
|
|
||||||
if (!this.server.isProxied()) {
|
|
||||||
System.err.println(address + " tried to use PROXY despite it being off.");
|
|
||||||
socket.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Arrays.equals(readUntilChar(' '),"ROXY".getBytes())) {
|
|
||||||
readUntilChar(' '); // proto
|
|
||||||
byte[] source = readUntilChar(' ');
|
|
||||||
address = new String(source);
|
|
||||||
readUntilChar(' '); // destination IP
|
|
||||||
readUntilChar(' '); // source port
|
|
||||||
readUntilChar('\r'); // destination port
|
|
||||||
if (input.read() != '\n') {
|
|
||||||
System.err.println("Invalid PROXY packet.");
|
|
||||||
socket.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.err.println("Invalid PROXY packet header.");
|
|
||||||
socket.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PID = readPID();
|
|
||||||
if (PID.length == 0) {
|
|
||||||
socket.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (PID[0]) {
|
switch (PID[0]) {
|
||||||
case MESSAGE -> {
|
case COMMUNICATION -> {
|
||||||
String msg = new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim();
|
String msg = new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim();
|
||||||
Date date = new Date();
|
Date date = new Date();
|
||||||
|
String address = socket.getInetAddress().getHostAddress();
|
||||||
|
|
||||||
String s = Sanitizer.sanitizeString(msg, true);
|
String s = Sanitizer.sanitizeString(msg, true);
|
||||||
String newContent = server.cache.content() + Sanitizer.formatMessage(date.getTime(), address, s);
|
String newContent = server.cache.content() + Sanitizer.formatMessage(date.getTime(), address, s);
|
||||||
@ -98,23 +68,9 @@ public class ServerThread implements Runnable {
|
|||||||
return input.readNBytes(1);
|
return input.readNBytes(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] readUntilChar(char c) throws IOException {
|
|
||||||
byte[] b = new byte[256];
|
|
||||||
int i;
|
|
||||||
for (i = 0;; i++) {
|
|
||||||
b[i] = (byte)input.read();
|
|
||||||
if (b[i] == c)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
byte[] r = new byte[i];
|
|
||||||
System.arraycopy(b, 0, r, 0, i);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendLogs(byte PID) throws IOException {
|
private void sendLogs(byte PID) throws IOException {
|
||||||
|
if (PID == COMMUNICATION) respond(server.cache.content());
|
||||||
if (PID == LOGS) {
|
if (PID == LOGS) {
|
||||||
respond(server.cache.content());
|
|
||||||
} else if (PID == CACHED_LOGS) {
|
|
||||||
String clientSize = Util.readAsciiNumber(in);
|
String clientSize = Util.readAsciiNumber(in);
|
||||||
int clientSizeNum = Integer.parseInt(clientSize);
|
int clientSizeNum = Integer.parseInt(clientSize);
|
||||||
byte[] serverLogs = server.cache.content().getBytes(StandardCharsets.UTF_8);
|
byte[] serverLogs = server.cache.content().getBytes(StandardCharsets.UTF_8);
|
||||||
|
Loading…
Reference in New Issue
Block a user