Skip to content

Commit

Permalink
Merge pull request #19 from rustymotors/nps-user-login
Browse files Browse the repository at this point in the history
refactor: Add NPS data handling and web request handling functions
  • Loading branch information
drazisil authored Jun 1, 2024
2 parents 613f249 + 410512f commit ede039b
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 6 deletions.
45 changes: 45 additions & 0 deletions packages/main/src/NPSMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NPSMessageHeader } from "./NPSMessageHeader.js";
import { NPSMessagePayload } from "./NPSMessagePayload.js";

export class NPSMessage {
constructor() {
this._header = new NPSMessageHeader();
this.data = new NPSMessagePayload();
}
/**
*
* @param {Buffer} data
* @returns {NPSMessage}
*/
static parse(data) {
const self = new NPSMessage();
if (data.length < 8) {
throw new Error(`Invalid message length: ${data.length}`);
}

self._header = NPSMessageHeader.parse(data);

const expectedLength = self._header.messageLength - self._header.dataOffset;

self.data = NPSMessagePayload.parse(
data.subarray(self._header.dataOffset),
expectedLength,
);

return self;
}

/**
* @returns Buffer
*/
toBuffer() {
return Buffer.concat([this._header.toBuffer(), this.data.toBuffer()]);
}

/**
* @returns string
*/
toString() {
return `${this._header.toString()}, Data: ${this.data.toString()}`;
}
}
69 changes: 69 additions & 0 deletions packages/main/src/NPSMessageHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
export class NPSMessageHeader {
constructor() {
this._dataStart = -1;
this.messageId = -1;
this.messageLength = -1;
this.version = -1;
}

/**
*
* @param {Buffer} data
* @returns NPSMessageHeader
*/
static parse(data) {
const self = new NPSMessageHeader();
if (data.length < 6) {
throw new Error("Invalid header length");
}
self.messageId = data.readUInt16BE(0);
self.messageLength = data.readUInt16BE(2);

self.version = data.readUInt16BE(4);

if (self.version === 257) {
self._dataStart = 12;
} else {
self._dataStart = 6;
}

return self;
}

get dataOffset() {
return this._dataStart;
}

/**
* @private
* @returns Buffer
*/
_writeExtraData() {
const buffer = Buffer.alloc(6);
buffer.writeUInt16BE(0, 0);
buffer.writeUInt32BE(this.messageLength, 2);
return buffer;
}

/**
* @returns Buffer
*/
toBuffer() {
let buffer = Buffer.alloc(6);
buffer.writeUInt16BE(this.messageId, 0);
buffer.writeUInt16BE(this.messageLength, 2);
buffer.writeUInt16BE(this.version, 4);

if (this.version === 257) {
return Buffer.concat([buffer, this._writeExtraData()]);
}
return buffer;
}

/**
* @returns string
*/
toString() {
return `ID: ${this.messageId}, Length: ${this.messageLength}, Version: ${this.version}`;
}
}
91 changes: 91 additions & 0 deletions packages/main/src/NPSMessagePayload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* @interface INPSPayload
*/
/**
* @interface INPSPayload
* @static parse
* @property {Buffer} data
*/
export class INPSPayload {
constructor() {
this.data = Buffer.alloc(0);
this.toBuffer = function () {};
this.toString = function () {};
}

/**
* @param {Buffer} data
* @returns INPSPayload
*/
static parse(data) {
const self = new NPSMessagePayload();
self.data = data;
return self;
}
}

/**
* To be used as a base class for NPS message payloads.
*
* @implements {INPSPayload}
* @class
* @property {Buffer} data
*
* @example
* class MyPayload extends NPSMessagePayload {
* constructor() {
* super();
* this.myProperty = 0;
* }
*
* static parse(data) {
* this.myProperty = data.readUInt32LE(0);
* }
*
* toBuffer() {
* const buffer = Buffer.alloc(4);
* buffer.writeUInt32LE(this.myProperty, 0);
* return buffer;
* }
*
* toString() {
* return `MyPayload: ${this.myProperty}`;
* }
* }
*/

export class NPSMessagePayload {
constructor() {
this.data = Buffer.alloc(0);
}

/**
*
* @param {Buffer} data
* @returns NPSMessagePayload
*/
static parse(data, len = data.length) {
if (data.length !== len) {
throw new Error(
`Invalid payload length: ${data.length}, expected: ${len}`,
);
}
const self = new NPSMessagePayload();
self.data = data;
return self;
}

/**
* @returns Buffer
*/
toBuffer() {
return this.data;
}

/**
* @returns string
*/
toString() {
return this.data.toString("hex");
}
}
73 changes: 73 additions & 0 deletions packages/main/src/NPSUserLoginPayload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { NPSMessagePayload } from "./NPSMessagePayload.js";

/**
* @typedef INPSPayload
* @type {import("./NPSMessagePayload.js").INPSPayload}
*/

/**
* @implements {INPSPayload}
* @extends {NPSMessagePayload}
* Payload for the NPSUserLogin message.
*/
export class NPSUserLoginPayload extends NPSMessagePayload {
constructor() {
super();
this.data = Buffer.alloc(0);
this.ticket = "";
this.sessionKey = "";
this.gameId = "";
}

/**
*
* @param {number} len
* @param {Buffer} data
* @returns {NPSUserLoginPayload}
*/
static parse(data, len = data.length) {
if (data.length !== len) {
throw new Error(
`Invalid payload length: ${data.length}, expected: ${len}`,
);
}

const self = new NPSUserLoginPayload();
try {
let offset = 0;
let nextLen = data.readUInt16BE(0);
self.ticket = data.toString("utf8", 2, nextLen + 2);
offset = nextLen + 2;
offset += 2; // Skip one empty word
nextLen = data.readUInt16BE(offset);
self.sessionKey = data.toString("hex", offset + 2, offset + 2 + nextLen);
offset += nextLen + 2;
nextLen = data.readUInt16BE(offset);
self.gameId = data
.subarray(offset + 2, offset + 2 + nextLen)
.toString("utf8");
} catch (error) {
if (!(error instanceof Error)) {
throw new Error(`Error parsing payload: ${error}`);
}
console.error(`Error parsing payload: ${error.message}`);
throw new Error(`Error parsing payload: ${error.message}`);
}

return self;
}

/**
* @returns {Buffer}
*/
toBuffer() {
throw new Error("Method not implemented.");
}

/**
* @returns {string}
*/
toString() {
return `Ticket: ${this.ticket}, SessionKey: ${this.sessionKey}, GameId: ${this.gameId}`;
}
}
17 changes: 14 additions & 3 deletions packages/main/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { UserLoginService } from "./UserLoginService.js";
import { WebServer } from "./WebServer.js";
import { onNPSData } from "./nps.js";
import { onWebRequest } from "./web.js";
import crypto from "node:crypto";
import * as Sentry from "@sentry/node";

/** @type {WebServer} */
let authServer;
Expand All @@ -33,12 +35,21 @@ let personaServer;

/**
* @param {import("node:net").Socket} socket
* @param {(port:number, data: Buffer) => void} onData
* @param {(port:number, data: Buffer, sendToClient: (data: Buffer) => void) => void} onData
*/
function onSocketConnection(socket, onData) {
console.log("Connection established");

const connectionId = crypto.randomUUID();

Sentry.setTag("connection_id", connectionId);

const sendToClient = (/** @type {Buffer} */ data) => {
socket.write(data);
};

socket.on("data", (data) => {
onData(socket.localPort ?? -1, data);
onData(socket.localPort ?? -1, data, sendToClient);
});
}

Expand Down Expand Up @@ -133,7 +144,7 @@ export default function main() {
"Rusty Motors",
"A test shard",
"10.10.5.20",
"Group - 1"
"Group - 1",
);

const userLoginService = new UserLoginService();
Expand Down
34 changes: 31 additions & 3 deletions packages/main/src/nps.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
import { NPSMessage } from "./NPSMessage.js";
import { NPSUserLoginPayload } from "./NPSUserLoginPayload.js";

/**
* @typedef INPSPayload
* @type {import("./NPSMessagePayload.js").INPSPayload}
*/

/** @type {Map<number, (data: Buffer, len: number) => INPSPayload>} */
const payloadMap = new Map();

payloadMap.set(1281, NPSUserLoginPayload.parse);

/**
* @param {number} port
* @param {Buffer} data
* @param {(data: Buffer) => void} sendToClient
*/
export function onNPSData(port, data) {
const hex = data.toString("hex");
console.log(`Data received: ${hex}`);
export function onNPSData(port, data, sendToClient) {
const message = NPSMessage.parse(data);
console.log(`Received message on port ${port}: ${message.toString()}`);

const messageType = payloadMap.get(message._header.messageId);

if (!messageType) {
console.error(`Unknown message type: ${message._header.messageId}`);
return;
}

const payload = messageType(
message.data.data,
message._header.messageLength - message._header.dataOffset,
);

console.log(`Parsed payload: ${payload.toString()}`);
}

0 comments on commit ede039b

Please sign in to comment.