diff --git a/lib/bedrock.js b/lib/bedrock.js index f9aa1b3..1b73818 100644 --- a/lib/bedrock.js +++ b/lib/bedrock.js @@ -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); } }); diff --git a/lib/varint.js b/lib/varint.js index cccd38e..d800c69 100644 --- a/lib/varint.js +++ b/lib/varint.js @@ -27,7 +27,7 @@ const varint = { buf.writeUInt8(byte | 0x80, written++); } - return buf.slice(0, written); + return buf.subarray(0, written); }, /** diff --git a/types/lib/bedrock.d.ts b/types/lib/bedrock.d.ts index ac58424..5b99e18 100644 --- a/types/lib/bedrock.d.ts +++ b/types/lib/bedrock.d.ts @@ -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; - +export function pingBedrock( + host: string, + options?: BedrockPingOptions +): Promise;