mirror of
https://github.com/minescope/mineping.git
synced 2025-07-01 09:38:15 +03:00
We use JSDoc for documentation, but these annotations were not being validated. This meant that type information could become outdated or incorrect without any warning. This commit introduces the TypeScript compiler (`tsc`) as a static analysis tool to leverage our existing JSDoc comments. To support this, JSDoc annotations across the codebase have been improved for accuracy. Additionally, the `varint` module now uses a custom `VarIntError` class for better type inference and error handling. A new `typecheck` script has been added to `package.json` to run this validation.
139 lines
3.7 KiB
JavaScript
139 lines
3.7 KiB
JavaScript
// https://minecraft.wiki/w/Java_Edition_protocol/Data_types
|
|
|
|
"use strict";
|
|
|
|
export const ERR_VARINT_BUFFER_UNDERFLOW = "VARINT_BUFFER_UNDERFLOW";
|
|
export const ERR_VARINT_MALFORMED = "VARINT_MALFORMED";
|
|
export const ERR_VARINT_ENCODE_TOO_LARGE = "VARINT_ENCODE_TOO_LARGE";
|
|
|
|
export class VarIntError extends Error {
|
|
/**
|
|
* @param {string} message The error message.
|
|
* @param {string} code The error code.
|
|
*/
|
|
constructor(message, code) {
|
|
super(message);
|
|
this.name = "VarIntError";
|
|
this.code = code;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encodes an integer into a VarInt buffer.
|
|
* VarInts are never longer than 5 bytes for the Minecraft protocol.
|
|
* @param {number} value The integer to encode
|
|
* @returns {Buffer} The encoded VarInt as a buffer
|
|
* @throws {VarIntError} if the value is too large to be encoded
|
|
*/
|
|
export function encodeVarInt(value) {
|
|
const buf = Buffer.alloc(5);
|
|
let written = 0;
|
|
let val = value;
|
|
|
|
while (true) {
|
|
const byte = val & 0x7f;
|
|
val >>>= 7;
|
|
|
|
if (val === 0) {
|
|
buf.writeUInt8(byte, written++);
|
|
break;
|
|
}
|
|
|
|
buf.writeUInt8(byte | 0x80, written++);
|
|
|
|
if (written >= 5 && val > 0) {
|
|
throw new VarIntError(
|
|
"Value too large for a 5-byte VarInt",
|
|
ERR_VARINT_ENCODE_TOO_LARGE
|
|
);
|
|
}
|
|
}
|
|
|
|
return buf.subarray(0, written);
|
|
}
|
|
|
|
/**
|
|
* Encodes a string into a UTF-8 buffer.
|
|
* @param {string} value The string to encode
|
|
* @returns {Buffer}
|
|
*/
|
|
export function encodeString(value) {
|
|
return Buffer.from(value, "utf-8");
|
|
}
|
|
|
|
/**
|
|
* Encodes an unsigned short (16-bit big-endian) into a 2-byte buffer.
|
|
* @param {number} value The number to encode
|
|
* @returns {Buffer}
|
|
*/
|
|
export function encodeUShort(value) {
|
|
const buf = Buffer.alloc(2);
|
|
buf.writeUInt16BE(value, 0);
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* Creates a Minecraft-style packet by concatenating chunks and prefixing the total length as a VarInt.
|
|
* @param {Buffer[]} chunks An array of buffers to include in the packet payload
|
|
* @returns {Buffer} The complete packet with its length prefix
|
|
*/
|
|
export function concatPackets(chunks) {
|
|
const payload = Buffer.concat(chunks);
|
|
const lengthPrefix = encodeVarInt(payload.length);
|
|
return Buffer.concat([lengthPrefix, payload]);
|
|
}
|
|
|
|
/**
|
|
* Decodes a VarInt from a buffer.
|
|
* Returns the decoded value and the number of bytes it consumed.
|
|
* @param {Buffer} buffer The buffer to read from
|
|
* @param {number} [offset=0] The starting offset in the buffer
|
|
* @returns {{ value: number, bytesRead: number }}
|
|
* @throws {VarIntError} if the buffer is too short or the VarInt is malformed
|
|
*/
|
|
export function decodeVarInt(buffer, offset = 0) {
|
|
if (offset >= buffer.length) {
|
|
throw new VarIntError(
|
|
"Buffer underflow: Cannot decode VarInt at or beyond buffer length.",
|
|
ERR_VARINT_BUFFER_UNDERFLOW
|
|
);
|
|
}
|
|
|
|
// Fast path for single-byte VarInts, which are very common.
|
|
const firstByte = buffer.readUInt8(offset);
|
|
if ((firstByte & 0x80) === 0) {
|
|
return { value: firstByte, bytesRead: 1 };
|
|
}
|
|
|
|
let val = firstByte & 0x7f; // Get the first 7 bits
|
|
let position = 7; // Bit position for the next byte's data
|
|
let bytesRead = 1; // We've read one byte so far
|
|
let currentOffset = offset + 1; // Start reading from the next
|
|
|
|
// Max 4 more bytes (total 5 bytes for a VarInt)
|
|
for (let i = 0; i < 4; i++) {
|
|
if (currentOffset >= buffer.length) {
|
|
throw new VarIntError(
|
|
"Buffer underflow: Incomplete VarInt, expected more bytes.",
|
|
ERR_VARINT_BUFFER_UNDERFLOW
|
|
);
|
|
}
|
|
|
|
const byte = buffer.readUInt8(currentOffset);
|
|
bytesRead++;
|
|
currentOffset++;
|
|
|
|
val |= (byte & 0x7f) << position;
|
|
position += 7;
|
|
|
|
if ((byte & 0x80) === 0) {
|
|
return { value: val, bytesRead: bytesRead };
|
|
}
|
|
}
|
|
|
|
throw new VarIntError(
|
|
"VarInt is too big or malformed: 5 bytes read with continuation bit still set.",
|
|
ERR_VARINT_MALFORMED
|
|
);
|
|
}
|