refactor: improve Bedrock error handling and validation

This commit is contained in:
Timofey Gelazoniya 2025-02-07 08:48:04 +03:00
parent c71236f223
commit 0959403b1b
Signed by: zeldon
GPG Key ID: 047886915281DD2A
3 changed files with 75 additions and 44 deletions

View File

@ -10,6 +10,7 @@ import crypto from "node:crypto";
const MAGIC = "00ffff00fefefefefdfdfdfd12345678";
const START_TIME = new Date().getTime();
const UNCONNECTED_PONG = 0x1c;
/**
* Creates an Unconnected Ping packet.
@ -30,12 +31,25 @@ const createUnconnectedPingFrame = (timestamp) => {
* Extract Modt from Unconnected Pong Packet and convert to an object
* @param {Buffer} unconnectedPongPacket
* @returns {Object}
* @throws {Error} If packet is malformed or invalid
* @see {@link https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Raknet_Protocol#Unconnected_Pong}
*/
const extractModt = (unconnectedPongPacket) => {
// Skip everything to Modt
if (
!Buffer.isBuffer(unconnectedPongPacket) ||
unconnectedPongPacket.length < 35
) {
throw new Error("Invalid pong packet");
}
const offset = 33;
const length = unconnectedPongPacket.readUInt16BE(offset);
// Check for buffer bounds
if (offset + 2 + length > unconnectedPongPacket.length) {
throw new Error("Malformed pong packet");
}
let modt = unconnectedPongPacket.toString(
"utf-8",
offset + 2,
@ -43,6 +57,12 @@ const extractModt = (unconnectedPongPacket) => {
);
const components = modt.split(";");
// Validate required components
if (components.length < 9) {
throw new Error("Invalid MODT format");
}
const parsedComponents = {
edition: components[0],
name: components[1],
@ -109,20 +129,23 @@ const ping = (host, port = 19132, cb, timeout = 5000) => {
}
socket.on("message", (pongPacket) => {
if (!Buffer.isBuffer(pongPacket) || pongPacket.length === 0) {
handleError(new Error("Invalid packet received"));
return;
}
const id = pongPacket[0];
if (id !== UNCONNECTED_PONG) {
handleError(new Error(`Unexpected packet ID: 0x${id.toString(16)}`));
return;
}
switch (id) {
case 0x1c: {
const modtObject = extractModt(pongPacket);
closeSocket();
cb(modtObject, null);
break;
}
default: {
handleError(new Error("Received unexpected packet"));
break;
}
try {
const modtObject = extractModt(pongPacket);
closeSocket();
cb(modtObject, null);
} catch (err) {
handleError(err);
}
});

View File

@ -27,7 +27,7 @@ const varint = {
buf.writeUInt8(byte | 0x80, written++);
}
return buf.slice(0, written);
return buf.subarray(0, written);
},
/**

View File

@ -1,53 +1,61 @@
/**
* @param port The server port.
* @param timeout The read/write socket timeout.
* @param port The server port (1-65535).
* @param timeout The read/write socket timeout in milliseconds.
*/
export type BedrockPingOptions = {
port?: number;
timeout?: number;
port?: number & { _brand: "Port" }; // 1-65535
timeout?: number & { _brand: "Timeout" }; // > 0
};
export type BedrockPingResponse = {
edition: string;
name: string;
version: {
protocolVersion: number;
minecraftVersion: string;
};
players: {
online: number;
max: number;
};
serverId: string;
mapName: string;
gameMode: string;
edition: string;
name: string;
version: {
protocolVersion: number;
minecraftVersion: string;
};
players: {
online: number;
max: number;
};
serverId: string;
mapName: string;
gameMode: string;
};
/**
* Asynchronously ping Minecraft Bedrock server.
*
* The optional `options` argument can be an object with a `ping` (default is `19132`) or/and `timeout` (default is `5000`) property.
*
*
* @param host The Bedrock server address.
* @param options The configuration for pinging Minecraft Bedrock server.
*
*
* ```js
* import { pingBedrock } from '@minescope/mineping';
*
*
* const data = await pingBedrock('mco.mineplex.com');
* console.log(data);
* ```
*
*
* The resulting output will resemble:
* ```console
* {
* version: { name: 'Mineplex', protocol: '475' },
* players: { max: '5207', online: '5206' },
* description: ' New Costumes',
* gamemode: 'Survival'
* edition: "MCPE",
* name: "Mineplex",
* version: {
* protocolVersion: 475,
* minecraftVersion: "1.18.0"
* },
* players: {
* online: 5206,
* max: 5207
* },
* serverId: "12345678",
* mapName: "Lobby",
* gameMode: "Survival"
* }
* ```
* @see [source](https://github.com/minescope/mineping/blob/915edbec9c9ad811459458600af3531ec0836911/lib/bedrock.js#L204)
*/
export function pingBedrock(host: string, options?: BedrockPingOptions): Promise<BedrockPingResponse>;
export function pingBedrock(
host: string,
options?: BedrockPingOptions
): Promise<BedrockPingResponse>;