From bd11068a17a5b02146127f57cb2db1c464e3c2f9 Mon Sep 17 00:00:00 2001 From: LucienHH Date: Fri, 11 Apr 2025 02:09:52 +0100 Subject: [PATCH] Move protocol to `node-nethernet` --- package.json | 4 +- src/createServer.js | 2 +- src/nethernet.js | 3 +- src/nethernet/client.js | 307 ------------------ src/nethernet/connection.js | 113 ------- src/nethernet/discovery/ServerData.js | 45 --- src/nethernet/discovery/crypto.js | 30 -- .../discovery/packets/MessagePacket.js | 31 -- src/nethernet/discovery/packets/Packet.js | 37 --- .../discovery/packets/RequestPacket.js | 23 -- .../discovery/packets/ResponsePacket.js | 29 -- src/nethernet/net.js | 22 -- src/nethernet/server.js | 229 ------------- src/nethernet/signalling.js | 27 -- src/nethernet/util.js | 10 - src/server/advertisement.js | 2 +- src/websocket/signal.js | 2 +- 17 files changed, 5 insertions(+), 911 deletions(-) delete mode 100644 src/nethernet/client.js delete mode 100644 src/nethernet/connection.js delete mode 100644 src/nethernet/discovery/ServerData.js delete mode 100644 src/nethernet/discovery/crypto.js delete mode 100644 src/nethernet/discovery/packets/MessagePacket.js delete mode 100644 src/nethernet/discovery/packets/Packet.js delete mode 100644 src/nethernet/discovery/packets/RequestPacket.js delete mode 100644 src/nethernet/discovery/packets/ResponsePacket.js delete mode 100644 src/nethernet/net.js delete mode 100644 src/nethernet/server.js delete mode 100644 src/nethernet/signalling.js delete mode 100644 src/nethernet/util.js diff --git a/package.json b/package.json index 2a65b44..9370eb3 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ ], "license": "MIT", "dependencies": { - "@jsprismarine/jsbinaryutils": "^5.5.3", "debug": "^4.3.1", "json-bigint": "^1.0.0", "jsonwebtoken": "^9.0.0", @@ -29,14 +28,13 @@ "minecraft-data": "^3.0.0", "minecraft-folder-path": "^1.2.0", "node-fetch": "^2.6.1", + "node-nethernet": "github:LucienHH/node-nethernet#protocol", "prismarine-auth": "github:LucienHH/prismarine-auth#playfab", "prismarine-nbt": "^2.0.0", "prismarine-realms": "^1.1.0", "protodef": "^1.14.0", "raknet-native": "^1.0.3", - "sdp-transform": "^2.14.2", "uuid-1345": "^1.0.2", - "werift": "^0.19.9", "ws": "^8.18.0", "xbox-rta": "^2.1.0" }, diff --git a/src/createServer.js b/src/createServer.js index 0dd4dc9..8e31263 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -4,7 +4,7 @@ const assert = require('assert') const { getRandomUint64 } = require('./datatypes/util') const { serverAuthenticate } = require('./client/auth') -const { SignalType } = require('./nethernet/signalling') +const { SignalType } = require('node-nethernet') /** @param {{ port?: number, version?: number, networkId?: string, transport?: string, delayedInit?: boolean }} options */ function createServer (options) { diff --git a/src/nethernet.js b/src/nethernet.js index 9a3b96b..c3a757a 100644 --- a/src/nethernet.js +++ b/src/nethernet.js @@ -1,6 +1,5 @@ const { waitFor } = require('./datatypes/util') -const { Client } = require('./nethernet/client') -const { Server } = require('./nethernet/server') +const { Client, Server } = require('node-nethernet') class NethernetClient { constructor (options = {}) { diff --git a/src/nethernet/client.js b/src/nethernet/client.js deleted file mode 100644 index 418e504..0000000 --- a/src/nethernet/client.js +++ /dev/null @@ -1,307 +0,0 @@ -const dgram = require('node:dgram') -const { write } = require('sdp-transform') -const { EventEmitter } = require('node:events') -const { RTCIceCandidate, RTCPeerConnection } = require('werift') - -const { Connection } = require('./connection') -const { SignalType, SignalStructure } = require('./signalling') - -const { getBroadcastAddress } = require('./net') -const { PACKET_TYPE } = require('./discovery/packets/Packet') -const { RequestPacket } = require('./discovery/packets/RequestPacket') -const { MessagePacket } = require('./discovery/packets/MessagePacket') -const { ResponsePacket } = require('./discovery/packets/ResponsePacket') -const { decrypt, encrypt, calculateChecksum } = require('./discovery/crypto') - -const { getRandomUint64 } = require('./util') - -const debug = require('debug')('minecraft-protocol') - -const PORT = 7551 -const BROADCAST_ADDRESS = getBroadcastAddress() - -class Client extends EventEmitter { - constructor (networkId) { - super() - - this.serverNetworkId = networkId - - this.networkId = getRandomUint64() - debug('C: Generated networkId:', this.networkId) - - this.connectionId = getRandomUint64() - debug('C: Generated connectionId:', this.connectionId) - - this.socket = dgram.createSocket('udp4') - - this.socket.on('message', (buffer, rinfo) => { - debug('C: Received message from', rinfo.address, ':', rinfo.port) - this.processPacket(buffer, rinfo) - }) - - this.responses = new Map() - this.addresses = new Map() - - this.credentials = [] - - this.signalHandler = this.sendDiscoveryMessage - - this.sendDiscoveryRequest() - debug('C: Sent initial discovery request') - - this.pingInterval = setInterval(() => { - debug('C: Sending periodic discovery request') - this.sendDiscoveryRequest() - }, 2000) - } - - async handleCandidate (signal) { - debug('C: Handling ICE candidate signal:', signal) - await this.rtcConnection.addIceCandidate(new RTCIceCandidate({ candidate: signal.data })) - } - - async handleAnswer (signal) { - debug('C: Handling answer signal:', signal) - await this.rtcConnection.setRemoteDescription({ type: 'answer', sdp: signal.data }) - } - - async createOffer () { - debug('C: Creating RTC offer') - this.rtcConnection = new RTCPeerConnection({ - iceServers: this.credentials - }) - - this.connection = new Connection(this, this.connectionId, this.rtcConnection) - - const candidates = [] - - this.rtcConnection.onicecandidate = (e) => { - if (e.candidate) { - debug('C: Collected ICE candidate:', e.candidate.candidate) - candidates.push(e.candidate.candidate) - } - } - - this.connection.setChannels( - this.rtcConnection.createDataChannel('ReliableDataChannel'), - this.rtcConnection.createDataChannel('UnreliableDataChannel') - ) - - this.connection.reliable.onopen = () => { this.emit('connected', this.connection) } - - this.rtcConnection.onconnectionstatechange = () => { - const state = this.rtcConnection.connectionState - debug('C: Connection state changed:', state) - if (state === 'disconnected') this.emit('disconnect', this.connectionId, 'disconnected') - } - - await this.rtcConnection.createOffer() - - const ice = this.rtcConnection.iceTransports[0] - const dtls = this.rtcConnection.dtlsTransports[0] - - if (!ice || !dtls) { - debug('C: Failed to create ICE or DTLS transports') - throw new Error('Failed to create transports') - } - - const iceParams = ice.iceGather.localParameters - const dtlsParams = dtls.localParameters - - if (dtlsParams.fingerprints.length === 0) { - debug('C: No DTLS fingerprints available') - throw new Error('local DTLS parameters has no fingerprints') - } - - const desc = write({ - version: 0, - origin: { - username: '-', - sessionId: getRandomUint64().toString(), - sessionVersion: 2, - netType: 'IN', - ipVer: 4, - address: '127.0.0.1' - }, - name: '-', - timing: { start: 0, stop: 0 }, - groups: [{ type: 'BUNDLE', mids: '0' }], - extmapAllowMixed: 'extmap-allow-mixed', - msidSemantic: { semantic: '', token: 'WMS' }, - media: [ - { - rtp: [], - fmtp: [], - type: 'application', - port: 9, - protocol: 'UDP/DTLS/SCTP', - payloads: 'webrtc-datachannel', - connection: { ip: '0.0.0.0', version: 4 }, - iceUfrag: iceParams.usernameFragment, - icePwd: iceParams.password, - iceOptions: 'trickle', - fingerprint: { type: dtlsParams.fingerprints[0].algorithm, hash: dtlsParams.fingerprints[0].value }, - setup: 'active', - mid: '0', - sctpPort: 5000, - maxMessageSize: 65536 - } - ] - }) - - await this.rtcConnection.setLocalDescription({ type: 'offer', sdp: desc }) - - debug('C: Local SDP set:', desc) - - this.signalHandler( - new SignalStructure(SignalType.ConnectRequest, this.connectionId, desc, this.serverNetworkId) - ) - - for (const candidate of candidates) { - debug('C: Sending ICE candidate signal:', candidate) - this.signalHandler( - new SignalStructure(SignalType.CandidateAdd, this.connectionId, candidate, this.serverNetworkId) - ) - } - } - - processPacket (buffer, rinfo) { - debug('C: Processing packet from', rinfo.address, ':', rinfo.port) - if (buffer.length < 32) { - debug('C: Received packet is too short') - throw new Error('Packet is too short') - } - - const decryptedData = decrypt(buffer.slice(32)) - const checksum = calculateChecksum(decryptedData) - - if (Buffer.compare(buffer.slice(0, 32), checksum) !== 0) { - debug('C: Checksum mismatch for packet from', rinfo.address) - throw new Error('Checksum mismatch') - } - - const packetType = decryptedData.readUInt16LE(2) - debug('C: Packet type:', packetType) - - switch (packetType) { - case PACKET_TYPE.DISCOVERY_REQUEST: - debug('C: Received DISCOVERY_REQUEST packet') - break - case PACKET_TYPE.DISCOVERY_RESPONSE: - debug('C: Received DISCOVERY_RESPONSE packet') - this.handleResponse(new ResponsePacket(decryptedData).decode(), rinfo) - break - case PACKET_TYPE.DISCOVERY_MESSAGE: - debug('C: Received DISCOVERY_MESSAGE packet') - this.handleMessage(new MessagePacket(decryptedData).decode()) - break - default: - debug('C: Unknown packet type:', packetType) - throw new Error('Unknown packet type') - } - } - - handleResponse (packet, rinfo) { - debug('C: Handling discovery response from', rinfo.address, 'with data:', packet) - this.addresses.set(packet.senderId, rinfo) - this.responses.set(packet.senderId, packet.data) - this.emit('pong', packet) - } - - handleMessage (packet) { - debug('C: Handling discovery message:', packet) - if (packet.data === 'Ping') { - debug('C: Ignoring ping message') - return - } - - const signal = SignalStructure.fromString(packet.data) - - signal.networkId = packet.senderId - - debug('C: Processing signal:', signal) - this.handleSignal(signal) - } - - handleSignal (signal) { - debug('C: Handling signal of type:', signal.type) - switch (signal.type) { - case SignalType.ConnectResponse: - debug('C: Handling ConnectResponse signal') - this.handleAnswer(signal) - break - case SignalType.CandidateAdd: - debug('C: Handling CandidateAdd signal') - this.handleCandidate(signal) - break - } - } - - sendDiscoveryRequest () { - debug('C: Sending discovery request') - const requestPacket = new RequestPacket() - - requestPacket.senderId = this.networkId - - requestPacket.encode() - - const buf = requestPacket.getBuffer() - - const packetToSend = Buffer.concat([calculateChecksum(buf), encrypt(buf)]) - - this.socket.send(packetToSend, PORT, BROADCAST_ADDRESS) - } - - sendDiscoveryMessage (signal) { - debug('C: Sending discovery message for signal:', signal) - const rinfo = this.addresses.get(signal.networkId) - - if (!rinfo) { - debug('C: No address found for networkId:', signal.networkId) - return - } - - const messagePacket = new MessagePacket() - - messagePacket.senderId = this.networkId - messagePacket.recipientId = BigInt(signal.networkId) - messagePacket.data = signal.toString() - messagePacket.encode() - - const buf = messagePacket.getBuffer() - - const packetToSend = Buffer.concat([calculateChecksum(buf), encrypt(buf)]) - - this.socket.send(packetToSend, rinfo.port, rinfo.address) - } - - async connect () { - debug('C: Initiating connection') - this.running = true - - await this.createOffer() - } - - send (buffer) { - this.connection.send(buffer) - } - - ping () { - debug('C: Sending ping') - - this.sendDiscoveryRequest() - } - - close (reason) { - debug('C: Closing client with reason:', reason) - if (!this.running) return - clearInterval(this.pingInterval) - this.connection?.close() - setTimeout(() => this.socket.close(), 100) - this.connection = null - this.running = false - this.removeAllListeners() - } -} - -module.exports = { Client } diff --git a/src/nethernet/connection.js b/src/nethernet/connection.js deleted file mode 100644 index 96ef0ec..0000000 --- a/src/nethernet/connection.js +++ /dev/null @@ -1,113 +0,0 @@ -const debug = require('debug')('minecraft-protocol') - -const MAX_MESSAGE_SIZE = 10_000 - -class Connection { - constructor (nethernet, address, rtcConnection) { - this.nethernet = nethernet - - this.address = address - - this.rtcConnection = rtcConnection - - this.reliable = null - - this.unreliable = null - - this.promisedSegments = 0 - - this.buf = Buffer.alloc(0) - } - - setChannels (reliable, unreliable) { - if (reliable) { - this.reliable = reliable - this.reliable.onmessage = (msg) => this.handleMessage(msg.data) - } - if (unreliable) { - this.unreliable = unreliable - } - } - - handleMessage (data) { - if (typeof data === 'string') { - data = Buffer.from(data) - } - - if (data.length < 2) { - throw new Error('Unexpected EOF') - } - - const segments = data[0] - - debug(`handleMessage segments: ${segments}`) - - data = data.subarray(1) - - if (this.promisedSegments > 0 && this.promisedSegments - 1 !== segments) { - throw new Error(`Invalid promised segments: expected ${this.promisedSegments - 1}, got ${segments}`) - } - - this.promisedSegments = segments - - this.buf = this.buf ? Buffer.concat([this.buf, data]) : data - - if (this.promisedSegments > 0) { - return - } - - this.nethernet.emit('encapsulated', this.buf, this.address) - - this.buf = null - } - - send (data) { - if (!this.reliable || this.reliable.readyState !== 'open') { - throw new Error('Reliable data channel is not available') - } - - let n = 0 - - if (typeof data === 'string') { - data = Buffer.from(data) - } - - let segments = Math.ceil(data.length / MAX_MESSAGE_SIZE) - - for (let i = 0; i < data.length; i += MAX_MESSAGE_SIZE) { - segments-- - - let end = i + MAX_MESSAGE_SIZE - if (end > data.length) end = data.length - - const frag = data.subarray(i, end) - const message = Buffer.concat([Buffer.from([segments]), frag]) - - debug('Sending fragment', segments, 'header', message[0]) - - this.reliable.send(message) - - n += frag.length - } - - if (segments !== 0) { - throw new Error('Segments count did not reach 0 after sending all fragments') - } - - return n - } - - close () { - if (this.reliable) { - this.reliable.close() - } - if (this.unreliable) { - this.unreliable.close() - } - if (this.rtcConnection) { - this.rtcConnection.close() - } - } -} - -module.exports = { Connection } diff --git a/src/nethernet/discovery/ServerData.js b/src/nethernet/discovery/ServerData.js deleted file mode 100644 index 3d003a5..0000000 --- a/src/nethernet/discovery/ServerData.js +++ /dev/null @@ -1,45 +0,0 @@ -const BinaryStream = require('@jsprismarine/jsbinaryutils').default - -class ServerData extends BinaryStream { - encode () { - this.writeByte(this.version) - this.writeString(this.motd) - this.writeString(this.levelName) - this.writeIntLE(this.gamemodeId) - this.writeIntLE(this.playerCount) - this.writeIntLE(this.playersMax) - this.writeBoolean(this.isEditorWorld) - this.writeBoolean(this.hardcore) - this.writeIntLE(this.transportLayer) - } - - decode () { - this.version = this.readByte() - this.motd = this.readString() - this.levelName = this.readString() - this.gamemodeId = this.readIntLE() - this.playerCount = this.readIntLE() - this.playersMax = this.readIntLE() - this.isEditorWorld = this.readBoolean() - this.hardcore = this.readBoolean() - this.transportLayer = this.readIntLE() - } - - readString () { - return this.read(this.readByte()).toString() - } - - writeString (v) { - this.writeByte(Buffer.byteLength(v)) - this.write(Buffer.from(v, 'utf-8')) - } - - prependLength () { - const buf = Buffer.alloc(2) - buf.writeUInt16LE(this.binary.length, 0) - this.binary = [...buf, ...this.binary] - this.writeIndex += 2 - } -} - -module.exports = { ServerData } diff --git a/src/nethernet/discovery/crypto.js b/src/nethernet/discovery/crypto.js deleted file mode 100644 index 8a4840a..0000000 --- a/src/nethernet/discovery/crypto.js +++ /dev/null @@ -1,30 +0,0 @@ -const crypto = require('node:crypto') - -const appIdBuffer = Buffer.allocUnsafe(8) -appIdBuffer.writeBigUInt64LE(BigInt(0xdeadbeef)) - -const AES_KEY = crypto.createHash('sha256') - .update(appIdBuffer) - .digest() - -function encrypt (data) { - const cipher = crypto.createCipheriv('aes-256-ecb', AES_KEY, null) - return Buffer.concat([cipher.update(data), cipher.final()]) -} - -function decrypt (data) { - const decipher = crypto.createDecipheriv('aes-256-ecb', AES_KEY, null) - return Buffer.concat([decipher.update(data), decipher.final()]) -} - -function calculateChecksum (data) { - const hmac = crypto.createHmac('sha256', AES_KEY) - hmac.update(data) - return hmac.digest() -} - -module.exports = { - encrypt, - decrypt, - calculateChecksum -} diff --git a/src/nethernet/discovery/packets/MessagePacket.js b/src/nethernet/discovery/packets/MessagePacket.js deleted file mode 100644 index 4625da6..0000000 --- a/src/nethernet/discovery/packets/MessagePacket.js +++ /dev/null @@ -1,31 +0,0 @@ -const { PACKET_TYPE, Packet } = require('./Packet') - -class MessagePacket extends Packet { - constructor (data) { - super(PACKET_TYPE.DISCOVERY_MESSAGE, data) - } - - encode () { - super.encode() - this.writeUnsignedLongLE(this.recipientId) - - this.writeUnsignedIntLE(this.data.length) - this.write(Buffer.from(this.data, 'utf-8')) - - this.prependLength() - - return this - } - - decode () { - super.decode() - this.recipientId = this.readUnsignedLongLE() - - const length = this.readUnsignedIntLE() - this.data = this.read(length).toString() - - return this - } -} - -module.exports = { MessagePacket } diff --git a/src/nethernet/discovery/packets/Packet.js b/src/nethernet/discovery/packets/Packet.js deleted file mode 100644 index 141d9a8..0000000 --- a/src/nethernet/discovery/packets/Packet.js +++ /dev/null @@ -1,37 +0,0 @@ -const PACKET_TYPE = { - DISCOVERY_REQUEST: 0, - DISCOVERY_RESPONSE: 1, - DISCOVERY_MESSAGE: 2 -} - -const BinaryStream = require('@jsprismarine/jsbinaryutils').default - -class Packet extends BinaryStream { - constructor (id, buffer) { - super(buffer) - - this.id = id - } - - encode () { - this.writeUnsignedShortLE(this.id) - this.writeUnsignedLongLE(this.senderId) - this.write(Buffer.alloc(8)) - } - - decode () { - this.packetLength = this.readUnsignedShortLE() - this.id = this.readUnsignedShortLE() - this.senderId = this.readUnsignedLongLE() - this.read(8) - } - - prependLength () { - const buf = Buffer.alloc(2) - buf.writeUInt16LE(this.binary.length, 0) - this.binary = [...buf, ...this.binary] - this.writeIndex += 2 - } -} - -module.exports = { PACKET_TYPE, Packet } diff --git a/src/nethernet/discovery/packets/RequestPacket.js b/src/nethernet/discovery/packets/RequestPacket.js deleted file mode 100644 index 3d2f589..0000000 --- a/src/nethernet/discovery/packets/RequestPacket.js +++ /dev/null @@ -1,23 +0,0 @@ -const { PACKET_TYPE, Packet } = require('./Packet') - -class RequestPacket extends Packet { - constructor (data) { - super(PACKET_TYPE.DISCOVERY_REQUEST, data) - } - - encode () { - super.encode() - - this.prependLength() - - return this - } - - decode () { - super.decode() - - return this - } -} - -module.exports = { RequestPacket } diff --git a/src/nethernet/discovery/packets/ResponsePacket.js b/src/nethernet/discovery/packets/ResponsePacket.js deleted file mode 100644 index c692582..0000000 --- a/src/nethernet/discovery/packets/ResponsePacket.js +++ /dev/null @@ -1,29 +0,0 @@ -const { PACKET_TYPE, Packet } = require('./Packet') - -class ResponsePacket extends Packet { - constructor (data) { - super(PACKET_TYPE.DISCOVERY_RESPONSE, data) - } - - encode () { - super.encode() - const hex = this.data.toString('hex') - - this.writeUnsignedIntLE(hex.length) - this.write(Buffer.from(hex, 'utf-8')) - - this.prependLength() - - return this - } - - decode () { - super.decode() - const length = this.readUnsignedIntLE() - this.data = Buffer.from(this.read(length).toString('utf-8'), 'hex') - - return this - } -} - -module.exports = { ResponsePacket } diff --git a/src/nethernet/net.js b/src/nethernet/net.js deleted file mode 100644 index 378321b..0000000 --- a/src/nethernet/net.js +++ /dev/null @@ -1,22 +0,0 @@ -const os = require('node:os') - -function getBroadcastAddress () { - const interfaces = os.networkInterfaces() - - for (const interfaceName in interfaces) { - for (const iface of interfaces[interfaceName]) { - // Only consider IPv4, non-internal (non-loopback) addresses - if (iface.family === 'IPv4' && !iface.internal) { - const ip = iface.address.split('.').map(Number) - const netmask = iface.netmask.split('.').map(Number) - const broadcast = ip.map((octet, i) => (octet | (~netmask[i] & 255))) - - return broadcast.join('.') // Return the broadcast address - } - } - } -} - -module.exports = { - getBroadcastAddress -} diff --git a/src/nethernet/server.js b/src/nethernet/server.js deleted file mode 100644 index 9398051..0000000 --- a/src/nethernet/server.js +++ /dev/null @@ -1,229 +0,0 @@ -const dgram = require('node:dgram') -const { EventEmitter } = require('node:events') -const { RTCIceCandidate, RTCPeerConnection } = require('werift') - -const { Connection } = require('./connection') -const { SignalStructure, SignalType } = require('./signalling') - -const { PACKET_TYPE } = require('./discovery/packets/Packet') -const { MessagePacket } = require('./discovery/packets/MessagePacket') -const { ResponsePacket } = require('./discovery/packets/ResponsePacket') -const { decrypt, encrypt, calculateChecksum } = require('./discovery/crypto') - -const { getRandomUint64 } = require('./util') - -const debug = require('debug')('minecraft-protocol') - -class Server extends EventEmitter { - constructor (options = {}) { - super() - - this.options = options - - this.networkId = options.networkId ?? getRandomUint64() - - this.connections = new Map() - - debug('S: Server initialised with networkId: %s', this.networkId) - } - - async handleCandidate (signal) { - const conn = this.connections.get(signal.connectionId) - - if (conn) { - debug('S: Adding ICE candidate for connectionId: %s', signal.connectionId) - await conn.rtcConnection.addIceCandidate(new RTCIceCandidate({ candidate: signal.data })) - } else { - debug('S: Received candidate for unknown connection', signal) - } - } - - async handleOffer (signal, respond, credentials = []) { - debug('S: Handling offer for connectionId: %s', signal.connectionId) - const rtcConnection = new RTCPeerConnection({ - iceServers: credentials - }) - - const connection = new Connection(this, signal.connectionId, rtcConnection) - - this.connections.set(signal.connectionId, connection) - - rtcConnection.onicecandidate = (e) => { - if (e.candidate) { - debug('S: ICE candidate generated for connectionId: %s', signal.connectionId) - respond( - new SignalStructure(SignalType.CandidateAdd, signal.connectionId, e.candidate.candidate, signal.networkId) - ) - } - } - - rtcConnection.ondatachannel = ({ channel }) => { - debug('S: Data channel established with label: %s', channel.label) - if (channel.label === 'ReliableDataChannel') connection.setChannels(channel) - if (channel.label === 'UnreliableDataChannel') connection.setChannels(null, channel) - } - - rtcConnection.onconnectionstatechange = () => { - const state = rtcConnection.connectionState - debug('S: Connection state changed for connectionId: %s, state: %s', signal.connectionId, state) - if (state === 'connected') this.emit('openConnection', connection) - if (state === 'disconnected') this.emit('closeConnection', signal.connectionId, 'disconnected') - } - - await rtcConnection.setRemoteDescription({ type: 'offer', sdp: signal.data }) - debug('S: Remote description set for connectionId: %s', signal.connectionId) - - const answer = await rtcConnection.createAnswer() - await rtcConnection.setLocalDescription(answer) - debug('S: Local description set (answer) for connectionId: %s', signal.connectionId) - - respond( - new SignalStructure(SignalType.ConnectResponse, signal.connectionId, answer.sdp, signal.networkId) - ) - } - - processPacket (buffer, rinfo) { - debug('S: Processing packet from %s:%s', rinfo.address, rinfo.port) - if (buffer.length < 32) { - debug('S: Packet is too short') - throw new Error('Packet is too short') - } - - const decryptedData = decrypt(buffer.slice(32)) - - const checksum = calculateChecksum(decryptedData) - - if (Buffer.compare(buffer.slice(0, 32), checksum) !== 0) { - debug('S: Checksum mismatch') - throw new Error('Checksum mismatch') - } - - const packetType = decryptedData.readUInt16LE(2) - - debug('S: Packet type: %s', packetType) - switch (packetType) { - case PACKET_TYPE.DISCOVERY_REQUEST: - debug('S: Handling discovery request') - this.handleRequest(rinfo) - break - case PACKET_TYPE.DISCOVERY_RESPONSE: - debug('S: Discovery response received (ignored)') - break - case PACKET_TYPE.DISCOVERY_MESSAGE: - debug('S: Handling discovery message') - this.handleMessage(new MessagePacket(decryptedData).decode(), rinfo) - break - default: - debug('S: Unknown packet type: %s', packetType) - throw new Error('Unknown packet type') - } - } - - setAdvertisement (buffer) { - debug('S: Setting advertisement data') - this.advertisement = buffer - } - - handleRequest (rinfo) { - debug('S: Handling request from %s:%s', rinfo.address, rinfo.port) - const data = this.advertisement - - if (!data) { - debug('S: Advertisement data not set') - return new Error('Advertisement data not set yet') - } - - const responsePacket = new ResponsePacket() - - responsePacket.senderId = this.networkId - responsePacket.data = data - - responsePacket.encode() - - const buf = responsePacket.getBuffer() - - const packetToSend = Buffer.concat([calculateChecksum(buf), encrypt(buf)]) - - this.socket.send(packetToSend, rinfo.port, rinfo.address) - debug('S: Response sent to %s:%s', rinfo.address, rinfo.port) - } - - handleMessage (packet, rinfo) { - debug('S: Handling message from %s:%s', rinfo.address, rinfo.port) - if (packet.data === 'Ping') { - debug('S: Ping message received') - return - } - - const respond = (signal) => { - debug('S: Responding with signal: %o', signal) - const messagePacket = new MessagePacket() - - messagePacket.senderId = this.networkId - messagePacket.recipientId = signal.networkId - messagePacket.data = signal.toString() - messagePacket.encode() - - const buf = messagePacket.getBuffer() - - const packetToSend = Buffer.concat([calculateChecksum(buf), encrypt(buf)]) - - this.socket.send(packetToSend, rinfo.port, rinfo.address) - debug('S: Signal response sent to %s:%s', rinfo.address, rinfo.port) - } - - const signal = SignalStructure.fromString(packet.data) - - signal.networkId = packet.senderId - - switch (signal.type) { - case SignalType.ConnectRequest: - debug('S: Handling ConnectRequest signal') - this.handleOffer(signal, respond) - break - case SignalType.CandidateAdd: - debug('S: Handling CandidateAdd signal') - this.handleCandidate(signal) - break - } - } - - async listen () { - debug('S: Starting server') - this.socket = dgram.createSocket('udp4') - - this.socket.on('message', (buffer, rinfo) => { - debug('S: Message received from %s:%s', rinfo.address, rinfo.port) - this.processPacket(buffer, rinfo) - }) - - await new Promise((resolve, reject) => { - const failFn = e => reject(e) - this.socket.once('error', failFn) - this.socket.bind(7551, () => { - debug('S: Server is listening on port 7551') - this.socket.removeListener('error', failFn) - resolve(true) - }) - }) - } - - send (buffer) { - this.connection.send(buffer) - } - - close (reason) { - debug('S: Closing server: %s', reason) - for (const conn of this.connections.values()) { - conn.close() - } - - this.socket.close(() => { - debug('S: Server closed') - this.emit('close', reason) - this.removeAllListeners() - }) - } -} - -module.exports = { Server } diff --git a/src/nethernet/signalling.js b/src/nethernet/signalling.js deleted file mode 100644 index 3b4e8fc..0000000 --- a/src/nethernet/signalling.js +++ /dev/null @@ -1,27 +0,0 @@ -const SignalType = { - ConnectRequest: 'CONNECTREQUEST', - ConnectResponse: 'CONNECTRESPONSE', - CandidateAdd: 'CANDIDATEADD', - ConnectError: 'CONNECTERROR' -} - -class SignalStructure { - constructor (type, connectionId, data, networkId) { - this.type = type - this.connectionId = connectionId - this.data = data - this.networkId = networkId - } - - toString () { - return `${this.type} ${this.connectionId} ${this.data}` - } - - static fromString (message) { - const [type, connectionId, ...data] = message.split(' ') - - return new this(type, BigInt(connectionId), data.join(' ')) - } -} - -module.exports = { SignalStructure, SignalType } diff --git a/src/nethernet/util.js b/src/nethernet/util.js deleted file mode 100644 index f420abd..0000000 --- a/src/nethernet/util.js +++ /dev/null @@ -1,10 +0,0 @@ -const getRandomUint64 = () => { - const high = Math.floor(Math.random() * 0xFFFFFFFF) - const low = Math.floor(Math.random() * 0xFFFFFFFF) - - return (BigInt(high) << 32n) | BigInt(low) -} - -module.exports = { - getRandomUint64 -} diff --git a/src/server/advertisement.js b/src/server/advertisement.js index 851a2e3..f7e50c8 100644 --- a/src/server/advertisement.js +++ b/src/server/advertisement.js @@ -1,6 +1,6 @@ const { Versions, CURRENT_VERSION } = require('../options') -const { ServerData } = require('../nethernet/discovery/ServerData') +const { ServerData } = require('node-nethernet') class NethernetServerAdvertisement { version = 3 diff --git a/src/websocket/signal.js b/src/websocket/signal.js index a9bd036..84b2d64 100644 --- a/src/websocket/signal.js +++ b/src/websocket/signal.js @@ -1,7 +1,7 @@ const { WebSocket } = require('ws') const { stringify } = require('json-bigint') const { once, EventEmitter } = require('node:events') -const { SignalStructure } = require('../nethernet/signalling') +const { SignalStructure } = require('node-nethernet') const debug = require('debug')('minecraft-protocol')