diff --git a/package.json b/package.json index 71c081f..b84c442 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "craftping", "type": "module", - "version": "2.0.0", + "version": "2.0.1", "main": "index.js", "repository": "github:aternosorg/craftping", "scripts": { diff --git a/src/BedrockPing/BedrockPing.js b/src/BedrockPing/BedrockPing.js index 959b157..3d62d66 100644 --- a/src/BedrockPing/BedrockPing.js +++ b/src/BedrockPing/BedrockPing.js @@ -1,16 +1,37 @@ import UDPClient from "../UDPSocket/UDPClient.js"; import UnconnectedPing from "../Packet/BedrockPing/UnconnectedPing.js"; import UnconnectedPong from "../Packet/BedrockPing/UnconnectedPong.js"; +import * as crypto from "node:crypto"; export default class BedrockPing extends UDPClient { + /** @type {BigInt} */ sessionId; + /** * @return {Promise} */ async ping() { + // Normally, the time field is used for the current ms timestamp, but we're using it as a session ID + // to identify which reply belongs to which request. + this.sessionId = crypto.randomBytes(8).readBigInt64BE(); let startTime = BigInt(Date.now()); - await this.sendPacket(new UnconnectedPing().setTime(startTime).generateClientGUID()); + await this.sendPacket(new UnconnectedPing().setTime(this.sessionId).generateClientGUID()); this.signal?.throwIfAborted(); - return new UnconnectedPong().read(await this.readData()); + // The time field in the response contains the session ID, but we replace it with the start time + // in case anyone relies on the time field containing an actual timestamp. + return new UnconnectedPong().read(await this.readData()).setTime(startTime); + } + + /** + * @inheritDoc + */ + shouldAcceptPacket(packet) { + let data = packet.getData(); + if (data.byteLength < 9) { + return false; + } + + let timestamp = data.readBigInt64BE(1); + return timestamp === this.sessionId; } } diff --git a/src/Query/Query.js b/src/Query/Query.js index 84c05fc..994c555 100644 --- a/src/Query/Query.js +++ b/src/Query/Query.js @@ -52,4 +52,17 @@ export default class Query extends UDPClient { this.signal?.throwIfAborted(); return new FullStatResponse().read(await this.readData()); } + + /** + * @inheritDoc + */ + shouldAcceptPacket(packet) { + let data = packet.getData(); + if (data.byteLength < 5) { + return false; + } + + let session = data.readUInt32BE(1); + return session === this.sessionId; + } } diff --git a/src/UDPSocket/UDPSocket.js b/src/UDPSocket/UDPSocket.js index aeae31c..4f1dccb 100644 --- a/src/UDPSocket/UDPSocket.js +++ b/src/UDPSocket/UDPSocket.js @@ -115,6 +115,9 @@ export default class UDPSocket extends EventEmitter { handleMessage(data, info) { let message = new UDPMessage(data, info); + if (!this.shouldAcceptPacket(message)) { + return this; + } if (this.readQueue.length > 0) { this.readQueue.shift().resolve(message); @@ -166,4 +169,12 @@ export default class UDPSocket extends EventEmitter { }); }); } + + /** + * @param {UDPMessage} packet + * @return {boolean} + */ + shouldAcceptPacket(packet) { + return true + } }