diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml
index 4a194fd59..cafde7f64 100644
--- a/.github/workflows/node.yml
+++ b/.github/workflows/node.yml
@@ -108,12 +108,6 @@ jobs:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
codecovcli --verbose do-upload --fail-on-error --flag persona --name persona --dir packages/persona
- - name: Codecov upload sessions coverage
- if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step
- env:
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- run: |
- codecovcli --verbose do-upload --fail-on-error --flag sessions --name sessions --dir packages/sessions
- name: Codecov upload shard coverage
if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step
env:
diff --git a/packages/core/src/serializationHelpers.test.ts b/packages/core/src/serializationHelpers.test.ts
deleted file mode 100644
index 2fe247f86..000000000
--- a/packages/core/src/serializationHelpers.test.ts
+++ /dev/null
@@ -1,335 +0,0 @@
-import { describe, expect, it } from "vitest";
-import {
- clamp16,
- clamp32,
- deserializeBool,
- deserializeByte,
- deserializeDWord,
- deserializeFloat,
- deserializeString,
- deserializeWord,
- serializeBool,
- serializeByte,
- serializeDWord,
- serializeFloat,
- serializeString,
- serializeWord,
- sizeOfBool,
- sizeOfByte,
- sizeOfDWord,
- sizeOfFloat,
- sizeOfString,
- sizeOfWord,
-} from "./serializationHelpers.js";
-
-describe("serializationHelpers", () => {
- describe("serializeBool()", () => {
- it("should serialize a boolean value", () => {
- // Arrange
- const input = true;
- const expected = Buffer.from([1]);
-
- // Act
- const actual = serializeBool(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
-
- it("should serialize a boolean value", () => {
- // Arrange
- const input = false;
- const expected = Buffer.from([0]);
-
- // Act
- const actual = serializeBool(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("deserializeBool()", () => {
- it("should deserialize a boolean value", () => {
- // Arrange
- const input = Buffer.from([1]);
- const expected = true;
-
- // Act
- const actual = deserializeBool(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
-
- it("should deserialize a boolean value", () => {
- // Arrange
- const input = Buffer.from([0]);
- const expected = false;
-
- // Act
- const actual = deserializeBool(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("sizeOfBool()", () => {
- it("should return the size of a boolean value", () => {
- // Arrange
- const expected = 1;
-
- // Act
- const actual = sizeOfBool();
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("serializeByte()", () => {
- it("should serialize a byte value", () => {
- // Arrange
- const input = 1;
- const expected = Buffer.from([1]);
-
- // Act
- const actual = serializeByte(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("deserializeByte()", () => {
- it("should deserialize a byte value", () => {
- // Arrange
- const input = Buffer.from([1]);
- const expected = 1;
-
- // Act
- const actual = deserializeByte(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("sizeOfByte()", () => {
- it("should return the size of a byte value", () => {
- // Arrange
- const expected = 1;
-
- // Act
- const actual = sizeOfByte();
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("serializeWord()", () => {
- it("should serialize a word value", () => {
- // Arrange
- const input = 1;
- const expected = Buffer.from([0, 1]);
-
- // Act
- const actual = serializeWord(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("deserializeWord()", () => {
- it("should deserialize a word value", () => {
- // Arrange
- const input = Buffer.from([0, 1]);
- const expected = 1;
-
- // Act
- const actual = deserializeWord(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("sizeOfWord()", () => {
- it("should return the size of a word value", () => {
- // Arrange
- const expected = 2;
-
- // Act
- const actual = sizeOfWord();
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("serializeDWord()", () => {
- it("should serialize a dword value", () => {
- // Arrange
- const input = 1;
- const expected = Buffer.from([0, 0, 0, 1]);
-
- // Act
- const actual = serializeDWord(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("deserializeDWord()", () => {
- it("should deserialize a dword value", () => {
- // Arrange
- const input = Buffer.from([0, 0, 0, 1]);
- const expected = 1;
-
- // Act
- const actual = deserializeDWord(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("sizeOfDWord()", () => {
- it("should return the size of a dword value", () => {
- // Arrange
- const expected = 4;
-
- // Act
- const actual = sizeOfDWord();
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("serializeFloat()", () => {
- it("should serialize a float value", () => {
- // Arrange
- const input = 1;
- const expected = Buffer.from([63, 128, 0, 0]);
-
- // Act
- const actual = serializeFloat(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("deserializeFloat()", () => {
- it("should deserialize a float value", () => {
- // Arrange
- const input = Buffer.from([63, 128, 0, 0]);
- const expected = 1;
-
- // Act
- const actual = deserializeFloat(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("sizeOfFloat()", () => {
- it("should return the size of a float value", () => {
- // Arrange
- const expected = 4;
-
- // Act
- const actual = sizeOfFloat();
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("serializeString()", () => {
- it("should serialize a string value", () => {
- // Arrange
- const input = "test";
- const expected = Buffer.from([0, 4, 116, 101, 115, 116]);
-
- // Act
- const actual = serializeString(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-
- describe("deserializeString()", () => {
- it("should deserialize a string value", () => {
- // Arrange
- const input = Buffer.from([0, 4, 116, 101, 115, 116]);
- const expected = "test";
-
- // Act
- const actual = deserializeString(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
-
- it("should throw an error if the size is bigger than the buffer length - 2", () => {
- // Arrange
- const input = Buffer.from([0, 5, 116, 101, 115, 116]);
-
- // Act
- const actual = () => deserializeString(input);
-
- // Assert
- expect(actual).toThrowError("Size is bigger than the buffer length - 2");
- });
- });
-
- describe("sizeOfString()", () => {
- it("should return the size of a string value", () => {
- // Arrange
- const input = "test";
- const expected = 6;
-
- // Act
- const actual = sizeOfString(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
- });
-});
-
-describe("clamp16()", () => {
- it("should clamp a value between 0 and 65535", () => {
- // Arrange
- const input = 65536;
- const expected = 65535;
-
- // Act
- const actual = clamp16(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
-});
-
-describe("clamp32()", () => {
- it("should clamp a value between 0 and 4294967295", () => {
- // Arrange
- const input = 4294967296;
- const expected = 4294967295;
-
- // Act
- const actual = clamp32(input);
-
- // Assert
- expect(actual).toEqual(expected);
- });
-});
diff --git a/packages/core/src/serializationHelpers.ts b/packages/core/src/serializationHelpers.ts
deleted file mode 100644
index 9e4185cf7..000000000
--- a/packages/core/src/serializationHelpers.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-import { ServerError } from "rusty-motors-shared";
-
-/**
- * Clamp a value between 0 and 255
- * @param {number} value
- * @returns {number}
- */
-export function clamp16(value: number): number {
- return Math.max(0, Math.min(65535, value));
-}
-
-/**
- * Clamp a value between 0 and 65535
- * @param {number} value
- * @returns {number}
- */
-export function clamp32(value: number): number {
- return Math.max(0, Math.min(4294967295, value));
-}
-
-/**
- * Serializes a boolean to a buffer.
- * @param {boolean} bool
- * @returns {Buffer}
- */
-export function serializeBool(bool: boolean): Buffer {
- const buf = Buffer.alloc(1);
-
- buf.writeUInt8(bool ? 1 : 0);
-
- return buf;
-}
-
-/**
- * Serializes a byte to a buffer.
- * @param {number} byte
- * @returns {Buffer}
- */
-export function serializeByte(byte: number): Buffer {
- const buf = Buffer.alloc(1);
-
- buf.writeUInt8(byte);
-
- return buf;
-}
-
-/**
- * Serializes a word to a buffer.
- * @param {number} word
- * @returns {Buffer}
- */
-export function serializeWord(word: number): Buffer {
- const buf = Buffer.alloc(2);
-
- buf.writeUInt16BE(word);
-
- return buf;
-}
-
-/**
- * Serializes a dword to a buffer.
- * @param {number} dword
- * @returns {Buffer}
- */
-export function serializeDWord(dword: number): Buffer {
- const buf = Buffer.alloc(4);
-
- buf.writeUInt32BE(dword);
-
- return buf;
-}
-
-/**
- * Serializes a float to a buffer.
- * @param {number} f
- * @returns {Buffer}
- */
-export function serializeFloat(f: number): Buffer {
- const buf = Buffer.alloc(4);
-
- buf.writeFloatBE(f);
-
- return buf;
-}
-
-/**
- * Serializes a string to a buffer. The buffer will be prefixed with the length of the string.
- * @param {string} str
- * @returns {Buffer}
- */
-export function serializeString(str: string): Buffer {
- const buf = Buffer.alloc(str.length + 2);
-
- buf.writeUInt16BE(str.length);
- buf.write(str, 2);
-
- return buf;
-}
-
-/**
- * Deserializes a boolean from a buffer.
- * @param {Buffer} buff
- * @returns {boolean}
- */
-export function deserializeBool(buff: Buffer): boolean {
- return buff.readUInt8() === 1;
-}
-
-/**
- * Deserializes a byte from a buffer.
- * @param {Buffer} buff
- * @returns {number}
- */
-export function deserializeByte(buff: Buffer): number {
- return buff.readUInt8();
-}
-
-/**
- * Deserializes a word from a buffer.
- * @param {Buffer} buff
- * @returns {number}
- */
-export function deserializeWord(buff: Buffer): number {
- return buff.readUInt16BE();
-}
-
-/**
- * Deserializes a dword from a buffer.
- * @param {Buffer} buff
- * @returns {number}
- */
-export function deserializeDWord(buff: Buffer): number {
- return buff.readUInt32BE();
-}
-
-/**
- * Deserializes a float from a buffer.
- * @param {Buffer} buff
- * @returns {number}
- */
-export function deserializeFloat(buff: Buffer): number {
- return buff.readFloatBE();
-}
-
-/**
- * Deserializes a string from a buffer. The buffer is expected to be prefixed with the length of the string.
- * @param {Buffer} buf
- * @returns {string}
- */
-export function deserializeString(buf: Buffer): string {
- const size = buf.readUInt16BE();
- if (size > buf.length - 2) {
- throw new ServerError("Size is bigger than the buffer length - 2");
- }
- const str = buf.subarray(2, size + 2).toString("utf8");
-
- return str;
-}
-
-export function sizeOfBool() {
- return 1;
-}
-
-export function sizeOfByte() {
- return 1;
-}
-
-export function sizeOfWord() {
- return 2;
-}
-
-export function sizeOfDWord() {
- return 4;
-}
-
-export function sizeOfFloat() {
- return 4;
-}
-
-/**
- * Returns the size of a string, including the length prefix.
- * @param {string} string
- * @returns {number}
- */
-export function sizeOfString(string: string): number {
- return string.length + 2;
-}
diff --git a/packages/gateway/index.ts b/packages/gateway/index.ts
index 60b379531..34622ec62 100644
--- a/packages/gateway/index.ts
+++ b/packages/gateway/index.ts
@@ -2,5 +2,4 @@ export { getGatewayServer, Gateway } from "./src/GatewayServer.js";
export {
createCommandEncryptionPair,
createDataEncryptionPair,
- verifyLegacyCipherSupport,
} from "./src/encryption.js";
diff --git a/packages/gateway/src/encryption.ts b/packages/gateway/src/encryption.ts
index f68a5fc8c..6ff9af740 100644
--- a/packages/gateway/src/encryption.ts
+++ b/packages/gateway/src/encryption.ts
@@ -14,36 +14,50 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-import { createCipheriv, createDecipheriv, getCiphers } from "node:crypto";
-import { McosEncryptionPair } from "rusty-motors-shared";
+import { createCipheriv, createDecipheriv } from "node:crypto";
+import {
+ McosEncryptionPair,
+ verifyLegacyCipherSupport,
+} from "rusty-motors-shared";
/**
* This function creates a new encryption pair for use with the game server
*
* @param {string} key The key to use for encryption
* @returns {McosEncryptionPair} The encryption pair
+ * @throws Error if the key is too short
+ * @throws Error if the server does not support the legacy ciphers
*/
export function createCommandEncryptionPair(key: string): McosEncryptionPair {
- if (key.length < 16) {
- throw Error("Key too short");
- }
+ try {
+ verifyLegacyCipherSupport();
+
+ if (key.length < 16) {
+ throw Error("Key too short");
+ }
- const sKey = key.slice(0, 16);
+ const sKey = key.slice(0, 16);
- // Deepcode ignore HardcodedSecret: This uses an empty IV
- const desIV = Buffer.alloc(8);
+ // Deepcode ignore HardcodedSecret: This uses an empty IV
+ const desIV = Buffer.alloc(8);
- const gsCipher = createCipheriv("des-cbc", Buffer.from(sKey, "hex"), desIV);
- gsCipher.setAutoPadding(false);
+ const gsCipher = createCipheriv("des-cbc", Buffer.from(sKey, "hex"), desIV);
+ gsCipher.setAutoPadding(false);
- const gsDecipher = createDecipheriv(
- "des-cbc",
- Buffer.from(sKey, "hex"),
- desIV,
- );
- gsDecipher.setAutoPadding(false);
+ const gsDecipher = createDecipheriv(
+ "des-cbc",
+ Buffer.from(sKey, "hex"),
+ desIV,
+ );
+ gsDecipher.setAutoPadding(false);
- return new McosEncryptionPair(gsCipher, gsDecipher);
+ return new McosEncryptionPair(gsCipher, gsDecipher);
+ } catch (error) {
+ const err = new Error(`Error creating command encryption pair: ${error}`, {
+ cause: error,
+ });
+ throw err;
+ }
}
/**
@@ -66,16 +80,3 @@ export function createDataEncryptionPair(key: string): McosEncryptionPair {
return new McosEncryptionPair(tsCipher, tsDecipher);
}
-
-/**
- * This function checks if the server supports the legacy ciphers
- *
- * @returns void
- * @throws Error if the server does not support the legacy ciphers
- */
-export function verifyLegacyCipherSupport() {
- const cipherList = getCiphers();
- if (!cipherList.includes("des-cbc") || !cipherList.includes("rc4")) {
- throw Error("Legacy ciphers not available");
- }
-}
diff --git a/packages/gateway/tsconfig.json b/packages/gateway/tsconfig.json
index a23e9e608..789270755 100644
--- a/packages/gateway/tsconfig.json
+++ b/packages/gateway/tsconfig.json
@@ -4,5 +4,5 @@
"incremental": true,
"composite": true
},
- "include": ["index.ts", "src"]
+ "include": ["index.ts", "src/**/*.ts"],
}
diff --git a/packages/sessions/index.ts b/packages/sessions/index.ts
deleted file mode 100644
index 6cd038bec..000000000
--- a/packages/sessions/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export {
- saveClientConnection,
- findClientByCustomerId,
- hasClientEncryptionPair,
- newClientConnection,
- setClientEncryption,
- clearConnectedClients,
-} from "./src/index.js";
diff --git a/packages/sessions/package.json b/packages/sessions/package.json
deleted file mode 100644
index f3a83908f..000000000
--- a/packages/sessions/package.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "name": "rusty-motors-sessions",
- "version": "1.0.0",
- "exports": {
- ".": {
- "import": "./index.js",
- "require": "./index.js"
- }
- },
- "type": "module",
- "scripts": {
- "check": "tsc",
- "lint": "npx @biomejs/biome lint --write .",
- "format": "npx @biomejs/biome format --write .",
- "test": "vitest run --coverage"
- },
- "keywords": [],
- "author": "",
- "license": "AGPL-3.0",
- "dependencies": {
- "@sentry/profiling-node": "8.33.1",
- "short-unique-id": "^5.2.0"
- },
- "description": "",
- "devDependencies": {
- "@vitest/coverage-v8": "2.1.2",
- "vitest": "^2.1.2"
- }
-}
diff --git a/packages/sessions/src/index.ts b/packages/sessions/src/index.ts
deleted file mode 100644
index e36c3b33f..000000000
--- a/packages/sessions/src/index.ts
+++ /dev/null
@@ -1,233 +0,0 @@
-import { createCipheriv, createDecipheriv } from "node:crypto";
-
-/**
- * Represents a pair of encryption and decryption functions.
- */
-type CipherPair = {
- /** The encryption function */
- encrypt: (data: Buffer) => Buffer;
- /** The decryption function */
- decrypt: (data: Buffer) => Buffer;
-};
-
-/**
- * Generates a pair of cipher and decipher functions for game encryption.
- * @returns The cipher and decipher functions.
- */
-function createGameEncryptionPair(key: string): CipherPair {
- try {
- assertStringIsHex(key);
- if (key.length !== 16) {
- throw Error(
- `Invalid game key length: ${key.length}. The key must be 16 bytes long.`,
- );
- }
-
- // The key used by the game 8 bytes long.
- // Since the key is in hex format, we need to slice it to 16 characters.
- key = key.slice(0, 16);
-
- // The IV is intentionally required to be all zeros.
- const iv = Buffer.alloc(8);
- const keyBuffer = Buffer.from(key, "hex");
-
- // The algorithm is intentionally set to "des-cbc".
- // This is because the game uses this insecure algorithm.
- // We are intentionally using an insecure algorithm here to match the game.
- const cipher = createCipheriv("des-cbc", keyBuffer, iv);
- const decipher = createDecipheriv("des-cbc", keyBuffer, iv);
-
- return {
- encrypt: cipher.update.bind(cipher),
- decrypt: decipher.update.bind(decipher),
- };
- } catch (error: unknown) {
- const err = new Error(`Failed to create game encryption pair`);
- err.cause = error;
- throw err;
- }
-}
-
-/**
- * Generates a pair of encryption and decryption functions for the server.
- *
- * @param key - The key to use for encryption and decryption. Must be 16 hex characters.
- * @returns {CipherPair} The encryption and decryption functions.
- */
-function createServerEncryptionPair(key: string): CipherPair {
- try {
- assertStringExists(key);
- assertStringIsHex(key);
- if (key.length !== 16) {
- throw Error(
- `Invalid server key length: ${key.length}. The key must be 16 bytes long.`,
- );
- }
-
- // The IV is intentionally required to be empty.
- const iv = Buffer.alloc(0);
- const keyBuffer = Buffer.from(key, "hex");
-
- // The algorithm is intentionally set to "rc4".
- // This is because the game uses this insecure algorithm.
- // We are intentionally using an insecure algorithm here to match the game.
- const cipher = createCipheriv("rc4", keyBuffer, iv);
- const decipher = createDecipheriv("rc4", keyBuffer, iv);
-
- return {
- encrypt: cipher.update.bind(cipher),
- decrypt: decipher.update.bind(decipher),
- };
- } catch (error: unknown) {
- const err = new Error(`Failed to create server encryption pair`);
- err.cause = error;
- throw err;
- }
-}
-
-type ConnectedClient = {
- /** The connection ID for the client */
- connectionId: string;
- /** The customer ID for the client */
- customerId: number;
- /** The session key for the client */
- sessionKey?: string;
- /** The game encryption pair for the client, if known */
- gameEncryptionPair?: ReturnType;
- /** The server encryption pair for the client, if known */
- serverEncryptionPair?: ReturnType;
- /** Whether the game encryption handshake is complete */
- gameEncryptionHandshakeComplete: boolean;
- /** Whether the server encryption handshake is complete */
- serverEncryptionHandshakeComplete: boolean;
-};
-
-/**
- * Sets the client encryption for a connected client.
- *
- * @param client - The connected client to set the encryption for.
- * @param sessionKey - The session key to associate with the client.
- * @returns The updated connected client with the encryption set.
- */
-export function setClientEncryption(
- client: ConnectedClient,
- sessionKey: string,
-): ConnectedClient {
- try {
- const gameEncryptionPair = createGameEncryptionPair(sessionKey);
- const serverEncryptionPair = createServerEncryptionPair(sessionKey);
- client.sessionKey = sessionKey;
- client.gameEncryptionPair = gameEncryptionPair;
- client.serverEncryptionPair = serverEncryptionPair;
- } catch (error: unknown) {
- const err = new Error(`Failed to set client encryption`);
- err.cause = error;
- throw err;
- }
- return client;
-}
-
-/**
- * Represents a record of connected clients.
- * The key is the connection ID.
- * The value is the connected client.
- */
-const connectedClients: Record = {};
-
-/**
- * Finds a connected client by their customer ID.
- *
- * @param customerId - The customer ID to search for.
- * @returns The connected client with the specified customer ID.
- * @throws Error if no client is found with the given customer ID.
- */
-export function findClientByCustomerId(customerId: number): ConnectedClient {
- const client = Object.values(connectedClients).find(
- (client) => client.customerId === customerId,
- );
- if (typeof client === "undefined") {
- throw new Error(`Client with customer ID ${customerId} not found`);
- }
- return client;
-}
-
-type connectionType = "game" | "server";
-
-/**
- * Checks if a client has an encryption pair based on the connection type.
- * @param client - The connected client.
- * @param connectionType - The type of connection ("game" or "server").
- * @returns A boolean indicating whether the client has an encryption pair.
- */
-export function hasClientEncryptionPair(
- client: ConnectedClient,
- connectionType: connectionType,
-): boolean {
- if (connectionType === "game") {
- return !!client.gameEncryptionPair;
- } else {
- return !!client.serverEncryptionPair;
- }
-}
-
-/**
- * Creates a new client connection.
- *
- * @param connectionId - The ID of the connection.
- * @param customerId - The ID of the customer.
- * @param sessionKey - The session key (optional).
- * @returns A ConnectedClient object representing the new client connection.
- */
-export function newClientConnection(
- connectionId: string,
- customerId: number,
- sessionKey?: string,
-): ConnectedClient {
- return {
- connectionId,
- customerId,
- sessionKey,
- gameEncryptionHandshakeComplete: false,
- serverEncryptionHandshakeComplete: false,
- };
-}
-
-/**
- * Saves the client connection with the specified connection ID.
- *
- * @param connectionId - The ID of the connection.
- * @param client - The connected client to be saved.
- */
-export function saveClientConnection(
- connectionId: string,
- client: ConnectedClient,
-): void {
- connectedClients[connectionId] = client;
-}
-
-/**
- * Clears all connected clients.
- */
-export function clearConnectedClients(): void {
- for (const connectionId in connectedClients) {
- delete connectedClients[connectionId];
- }
-}
-
-function assertStringExists(str: string): void {
- if (str === "" || typeof str === "undefined") {
- throw new Error("String not provided");
- }
-}
-
-/**
- * Asserts that a given string is a valid hexadecimal string.
- *
- * @param str - The string to be validated.
- * @throws {Error} If the string is not a valid hexadecimal string.
- */
-function assertStringIsHex(str: string): void {
- if (!/^[0-9a-fA-F]+$/.test(str)) {
- throw new Error(`Invalid hex string: ${str}`);
- }
-}
diff --git a/packages/sessions/test/index.test.ts b/packages/sessions/test/index.test.ts
deleted file mode 100644
index 57070ffa3..000000000
--- a/packages/sessions/test/index.test.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { beforeEach, describe, expect, it } from "vitest";
-import {
- saveClientConnection,
- clearConnectedClients,
- findClientByCustomerId,
- hasClientEncryptionPair,
- newClientConnection,
- setClientEncryption,
-} from "../index.js";
-
-describe("Client connections", () => {
- beforeEach(() => {
- clearConnectedClients();
- });
-
- describe("newClientConnection", () => {
- it("should create a new client connection", () => {
- const connectionId = "123";
- const customerId = 456;
-
- const client = newClientConnection(connectionId, customerId);
-
- expect(client.connectionId).toBe(connectionId);
- expect(client.customerId).toBe(customerId);
- expect(client.gameEncryptionHandshakeComplete).toBe(false);
- expect(client.serverEncryptionHandshakeComplete).toBe(false);
- });
- });
-
- describe("saveClientConnection", () => {
- it("should save a client connection", () => {
- const connectionId = "123";
- const customerId = 456;
- const client = newClientConnection(connectionId, customerId);
-
- saveClientConnection(connectionId, client);
-
- expect(findClientByCustomerId(customerId)).toBe(client);
- });
- });
-
- describe("findClientByCustomerId", () => {
- it("should find a client by customer ID", () => {
- const connectionId = "123";
- const customerId = 456;
- const client = newClientConnection(connectionId, customerId);
- saveClientConnection(connectionId, client);
-
- expect(findClientByCustomerId(customerId)).toBe(client);
- });
-
- it("should throw an error if the client is not found", () => {
- const customerId = 456;
-
- expect(() => findClientByCustomerId(customerId)).toThrow(
- `Client with customer ID ${customerId} not found`,
- );
- });
- });
-
- describe("setClientEncryption", () => {
- it("should set the client encryption pair", () => {
- const connectionId = "123";
- const customerId = 456;
- const sessionKey = "ea25e21a2a022d71";
-
- const client = newClientConnection(connectionId, customerId);
- saveClientConnection(connectionId, client);
-
- setClientEncryption(client, sessionKey);
-
- expect(client.sessionKey).toBe(sessionKey);
- });
-
- it("should throw an error if the session key is not provided", () => {
- const connectionId = "123";
- const customerId = 456;
-
- const client = newClientConnection(connectionId, customerId);
- saveClientConnection(connectionId, client);
-
- expect(() => setClientEncryption(client, "")).toThrow();
- });
-
- it("should throw an error if the session key is invalid", () => {
- const connectionId = "123";
- const customerId = 456;
-
- const client = newClientConnection(connectionId, customerId);
- saveClientConnection(connectionId, client);
-
- expect(() => setClientEncryption(client, "invalid")).toThrow();
- });
- });
-
- describe("hasClientEncryptionPair", () => {
- it("should return true if the client has an encryption pair", () => {
- const connectionId = "123";
- const customerId = 456;
- const sessionKey = "ea25e21a2a022d71";
- const client = newClientConnection(connectionId, customerId);
- saveClientConnection(connectionId, client);
-
- expect(hasClientEncryptionPair(client, "game")).toBe(false);
- expect(hasClientEncryptionPair(client, "server")).toBe(false);
-
- setClientEncryption(client, sessionKey);
-
- expect(hasClientEncryptionPair(client, "game")).toBe(true);
- expect(hasClientEncryptionPair(client, "server")).toBe(true);
- });
- });
-
- describe("clearConnectedClients", () => {
- it("should clear all connected clients", () => {
- const connectionId = "123";
- const customerId = 456;
- const client = newClientConnection(connectionId, customerId);
- saveClientConnection(connectionId, client);
-
- clearConnectedClients();
-
- expect(() => findClientByCustomerId(customerId)).toThrow(
- `Client with customer ID ${customerId} not found`,
- );
- });
- });
-});
diff --git a/packages/sessions/tsconfig.json b/packages/sessions/tsconfig.json
deleted file mode 100644
index 6a3ee5ec8..000000000
--- a/packages/sessions/tsconfig.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "extends": "./../../tsconfig.base.json",
- "compilerOptions": {
- "incremental": true,
- "composite": true
- },
- "include": ["index.ts", "src", "test"]
-}
diff --git a/packages/shared-packets/src/GameMessageHeader.ts b/packages/shared-packets/src/GameMessageHeader.ts
index 09095023e..be5dad1b5 100644
--- a/packages/shared-packets/src/GameMessageHeader.ts
+++ b/packages/shared-packets/src/GameMessageHeader.ts
@@ -2,9 +2,12 @@ import { BufferSerializer } from "./BufferSerializer.js";
import type { SerializableInterface } from "./types.js";
/**
- *
+ * Represents the header of a game message.
+ * The header contains the message ID, the length of the message data,
+ * and the version of the message.
+ *
+ * This is a big-endian structure.
*/
-
export class GameMessageHeader
extends BufferSerializer
implements SerializableInterface
diff --git a/packages/shared-packets/src/GameMessagePayload.ts b/packages/shared-packets/src/GameMessagePayload.ts
index baffa4301..35b343949 100644
--- a/packages/shared-packets/src/GameMessagePayload.ts
+++ b/packages/shared-packets/src/GameMessagePayload.ts
@@ -9,7 +9,7 @@ export class GameMessagePayload
static copy(payload: GameMessagePayload): GameMessagePayload {
const newPayload = new GameMessagePayload();
- newPayload._data = Buffer.from(payload._data);
+ newPayload.deserialize(payload.serialize());
return newPayload;
}
diff --git a/packages/shared-packets/src/GamePacket.ts b/packages/shared-packets/src/GamePacket.ts
index 62c99fda1..c77c5b0e4 100644
--- a/packages/shared-packets/src/GamePacket.ts
+++ b/packages/shared-packets/src/GamePacket.ts
@@ -20,14 +20,14 @@ export class GamePacket extends BasePacket implements SerializableMessage {
* @returns A new `ServerPacket` instance with the same message ID and header as the original,
* and either the deserialized new data or a copy of the original data.
*/
- static copy(originalPacket: GamePacket, newData: Buffer): GamePacket {
+ static copy(originalPacket: GamePacket, newData?: Buffer): GamePacket {
const newPacket = new GamePacket();
newPacket.deserialize(originalPacket.serialize());
if (newData) {
newPacket.data.deserialize(newData);
} else {
- newPacket.data = GameMessagePayload.copy(originalPacket.data);
+ newPacket.data.deserialize(originalPacket.data.serialize());
}
return newPacket;
diff --git a/packages/shared-packets/src/ServerMessageHeader.ts b/packages/shared-packets/src/ServerMessageHeader.ts
index 3854baf0b..7693192e1 100644
--- a/packages/shared-packets/src/ServerMessageHeader.ts
+++ b/packages/shared-packets/src/ServerMessageHeader.ts
@@ -2,7 +2,11 @@ import { BufferSerializer } from "./BufferSerializer.js";
import type { SerializableInterface } from "./types.js";
/**
- *
+ * Represents the header of a server message.
+ * The header contains the length of the message data,
+ * the signature of the message,
+ *
+ * This is a little-endian structure.
*/
export class ServerMessageHeader
diff --git a/packages/shared-packets/src/ServerMessagePayload.ts b/packages/shared-packets/src/ServerMessagePayload.ts
index e5c721592..c44d1cd33 100644
--- a/packages/shared-packets/src/ServerMessagePayload.ts
+++ b/packages/shared-packets/src/ServerMessagePayload.ts
@@ -5,15 +5,13 @@ export class ServerMessagePayload
extends BufferSerializer
implements SerializableInterface
{
- public messageId: number = 0; // 2 bytes
-
+ private messageId: number = 0; // 2 bytes
private previousMessageId: number = 0; // Not serialized
private isEncrypted: boolean = false; // Not serialized
static copy(payload: ServerMessagePayload): ServerMessagePayload {
const newPayload = new ServerMessagePayload();
- newPayload.messageId = payload.messageId;
- newPayload._data = Buffer.from(payload._data);
+ newPayload.deserialize(payload.serialize());
return newPayload;
}
diff --git a/packages/shared-packets/src/ServerPacket.ts b/packages/shared-packets/src/ServerPacket.ts
index 5b58700cb..f49051528 100644
--- a/packages/shared-packets/src/ServerPacket.ts
+++ b/packages/shared-packets/src/ServerPacket.ts
@@ -20,7 +20,7 @@ export class ServerPacket extends BasePacket implements SerializableMessage {
* @returns A new `ServerPacket` instance with the same message ID and header as the original,
* and either the deserialized new data or a copy of the original data.
*/
- static copy(originalPacket: ServerPacket, newData: Buffer): ServerPacket {
+ static copy(originalPacket: ServerPacket, newData?: Buffer): ServerPacket {
const newPacket = new ServerPacket();
newPacket.header = ServerMessageHeader.copy(originalPacket.header);
diff --git a/packages/shared-packets/test/GamePacket.test.ts b/packages/shared-packets/test/GamePacket.test.ts
index 44f40cad6..d49f2f9a2 100644
--- a/packages/shared-packets/test/GamePacket.test.ts
+++ b/packages/shared-packets/test/GamePacket.test.ts
@@ -3,7 +3,7 @@ import { Buffer } from "buffer";
import { GamePacket } from "../src/GamePacket.js";
describe("GamePacket", () => {
- it("should deserialize correctly v0 correctly", () => {
+ it("should deserialize v0 correctly", () => {
const buffer = Buffer.alloc(11);
buffer.writeUInt16BE(1234, 0); // Message ID
buffer.writeUInt16BE(11, 2); // Length
@@ -19,7 +19,7 @@ describe("GamePacket", () => {
);
});
- it("should deserialize correctly v1 correctly", () => {
+ it("should deserialize v1 correctly", () => {
const buffer = Buffer.alloc(26);
buffer.writeUInt16BE(1234, 0); // Message ID
buffer.writeUInt16BE(11, 2); // Length
@@ -36,6 +36,32 @@ describe("GamePacket", () => {
);
});
+ it("should be able to make a copy of the packet", () => {
+ const buffer = Buffer.alloc(11);
+ buffer.writeUInt16BE(1234, 0); // Message ID
+ buffer.writeUInt16BE(11, 2); // Length
+ buffer.write("test da", 4); // Data
+
+ const packet = new GamePacket();
+ packet.deserialize(buffer);
+
+ const copy = GamePacket.copy(packet);
+ expect(copy.serialize().toString("hex")).equals(packet.serialize().toString("hex"));
+ });
+
+ it("should be able to make a copy of the packet with new data", () => {
+ const buffer = Buffer.alloc(11);
+ buffer.writeUInt16BE(1234, 0); // Message ID
+ buffer.writeUInt16BE(11, 2); // Length
+ buffer.write("test da", 4); // Data
+
+ const packet = new GamePacket();
+ packet.deserialize(buffer);
+
+ const copy = GamePacket.copy(packet, Buffer.from("new data"));
+ expect(copy.serialize().toString("hex")).not.equals(packet.serialize().toString("hex"));
+ });
+
it("should throw error if data is insufficient for header", () => {
const buffer = Buffer.alloc(5); // Less than required for header
@@ -55,7 +81,7 @@ describe("GamePacket", () => {
);
});
- it("should identify version correctly", () => {
+ it("should identify version v1 correctly", () => {
const buffer = Buffer.alloc(15);
buffer.writeUInt16BE(11, 0); // Length
buffer.writeUInt16BE(0x101, 4); // Version
@@ -68,7 +94,7 @@ describe("GamePacket", () => {
expect(packet.getVersion()).toBe(257);
});
- it("should handle version 0 correctly", () => {
+ it("should handle version v0 correctly", () => {
const buffer = Buffer.alloc(15);
buffer.writeUInt16BE(1234, 0); // Message ID
buffer.writeUInt16BE(11, 4); // Length
diff --git a/packages/shared-packets/src/ServerPacket.test.ts b/packages/shared-packets/test/ServerPacket.test.ts
similarity index 93%
rename from packages/shared-packets/src/ServerPacket.test.ts
rename to packages/shared-packets/test/ServerPacket.test.ts
index 349db6c0f..e2ec5f8c4 100644
--- a/packages/shared-packets/src/ServerPacket.test.ts
+++ b/packages/shared-packets/test/ServerPacket.test.ts
@@ -1,7 +1,7 @@
import { describe, it, expect } from "vitest";
import { Buffer } from "buffer";
-import { ServerMessagePayload } from "./ServerMessagePayload.js";
-import { ServerPacket } from "./ServerPacket.js";
+import { ServerMessagePayload } from "../src/ServerMessagePayload.js";
+import { ServerPacket } from "../src/ServerPacket.js";
describe("ServerMessagePayload", () => {
it("should serialize correctly", () => {
@@ -108,7 +108,9 @@ describe("ServerMessagePayload", () => {
packet.setPayloadEncryption(true);
const str = packet.toString();
- expect(str).toBe("ServerPacket {length: 11, sequence: 5678, messageId: 1234}");
+ expect(str).toBe(
+ "ServerPacket {length: 11, sequence: 5678, messageId: 1234}",
+ );
});
});
});
diff --git a/packages/shared-packets/tsconfig.json b/packages/shared-packets/tsconfig.json
index ba43189cf..39f6a71bb 100644
--- a/packages/shared-packets/tsconfig.json
+++ b/packages/shared-packets/tsconfig.json
@@ -4,5 +4,5 @@
"incremental": true,
"composite": true
},
- "include": ["index.ts", "src"]
+ "include": ["index.ts", "src/**/*.ts"],
}
diff --git a/packages/shared/index.ts b/packages/shared/index.ts
index 516bfdca5..071b27dce 100644
--- a/packages/shared/index.ts
+++ b/packages/shared/index.ts
@@ -34,6 +34,7 @@ export {
findSessionByConnectionId,
updateEncryption,
} from "./src/State.js";
+export { ensureLegacyCipherCompatibility as verifyLegacyCipherSupport } from "./src/verifyLegacyCipherSupport.js";
export type { State } from "./src/State.js";
export type { OnDataHandler, ServiceResponse } from "./src/State.js";
export { LegacyMessage } from "./src/LegacyMessage.js";
@@ -57,7 +58,12 @@ export interface ConnectionRecord {
}
// Function to convert ARGB to 32-bit integer
-export function argbToInt(alpha: number, red: number, green: number, blue: number) {
+export function argbToInt(
+ alpha: number,
+ red: number,
+ green: number,
+ blue: number,
+) {
return (
((alpha & 0xff) << 24) |
((red & 0xff) << 16) |
diff --git a/packages/shared/src/verifyLegacyCipherSupport.ts b/packages/shared/src/verifyLegacyCipherSupport.ts
new file mode 100644
index 000000000..93f5ef4d7
--- /dev/null
+++ b/packages/shared/src/verifyLegacyCipherSupport.ts
@@ -0,0 +1,15 @@
+import { getCiphers } from "node:crypto";
+
+/**
+ * This function checks if the server supports the legacy ciphers
+ *
+ * @returns void
+ * @throws Error if the server does not support the legacy ciphers
+ */
+
+export function ensureLegacyCipherCompatibility() {
+ const cipherList = getCiphers();
+ if (!cipherList.includes("des-cbc") || !cipherList.includes("rc4")) {
+ throw new Error("Legacy ciphers not available");
+ }
+}
diff --git a/server.ts b/server.ts
index dfd405097..af36a6825 100755
--- a/server.ts
+++ b/server.ts
@@ -17,7 +17,7 @@
import { exit } from "node:process";
import * as Sentry from "@sentry/node";
import { getGatewayServer } from "rusty-motors-gateway";
-import { verifyLegacyCipherSupport } from "rusty-motors-gateway";
+import { verifyLegacyCipherSupport } from "rusty-motors-shared";
import { getServerConfiguration } from "rusty-motors-shared";
import { getServerLogger } from "rusty-motors-shared";
diff --git a/test/factoryMocks.ts b/test/factoryMocks.ts
index e5c7c7b58..b8bb1eafb 100644
--- a/test/factoryMocks.ts
+++ b/test/factoryMocks.ts
@@ -1,6 +1,5 @@
import { expect, it, vi } from "vitest";
-import { verifyLegacyCipherSupport } from "../packages/gateway/src/encryption.js";
-
+import { ensureLegacyCipherCompatibility } from "../packages/shared/src/verifyLegacyCipherSupport.js";
export function mockPino() {
vi.mock("pino", () => {
@@ -30,5 +29,5 @@ export function unmockPino() {
}
it("should have crypto", () => {
- expect(() => verifyLegacyCipherSupport()).not.toThrow();
+ expect(() => ensureLegacyCipherCompatibility()).not.toThrow();
});