jsp-raknet
Version:
Basic RakNet implementation written in Javascript
424 lines (423 loc) • 17.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Connection = void 0;
const jsbinaryutils_1 = __importDefault(require("@jsprismarine/jsbinaryutils"));
const ACK_1 = __importDefault(require("./protocol/ACK"));
const bitflags_1 = __importDefault(require("./protocol/bitflags"));
const ConnectedPing_1 = __importDefault(require("./protocol/ConnectedPing"));
const ConnectedPong_1 = __importDefault(require("./protocol/ConnectedPong"));
const ConnectionRequest_1 = __importDefault(require("./protocol/ConnectionRequest"));
const ConnectionRequestAccepted_1 = __importDefault(require("./protocol/ConnectionRequestAccepted"));
const DataPacket_1 = require("./protocol/DataPacket");
const EncapsulatedPacket_1 = __importDefault(require("./protocol/EncapsulatedPacket"));
const Identifiers_1 = __importDefault(require("./protocol/Identifiers"));
const NACK_1 = __importDefault(require("./protocol/NACK"));
const NewIncomingConnection_1 = __importDefault(require("./protocol/NewIncomingConnection"));
const reliability_1 = __importDefault(require("./protocol/reliability"));
const Server_1 = require("./Server");
const { SlidingReceiveWindow, SlidingOrderedWindow } = require('./SlidingWindow');
const debug = require('debug')('raknet');
var Priority;
(function (Priority) {
Priority[Priority["Normal"] = 0] = "Normal";
Priority[Priority["Immediate"] = 1] = "Immediate";
})(Priority || (Priority = {}));
const CONNECTION_TIMEOUT = 10000;
class Connection {
constructor(listener, mtuSize, address) {
this.state = 0 /* Connecting */;
this.nackQueue = new Set();
this.ackQueue = [];
this.nacking = new Set();
this.nextDatagram = new DataPacket_1.DataPacket();
this.recvQueue = [];
this.splitPackets = new Map();
this.recoveryList = new Map();
this.receiveWindow = new SlidingReceiveWindow(256);
this.reliableReceiveWindow = new SlidingOrderedWindow(256);
// TODO: ReliableOrdered channels
this.channelIndex = [];
this.lastUpdate = Date.now();
this.sendMessageIndex = 0;
this.sendSequenceNumber = 0;
this.sendSplitID = 0;
this.running = true;
this.listener = listener;
this.mtuSize = mtuSize;
this.address = address;
this.inLog = (...args) => debug('-> ', ...args);
this.outLog = (...args) => debug('<- ', ...args);
for (let i = 0; i < 32; i++) {
this.channelIndex[i] = 0;
}
}
/**
* Called by listener to run connection ops
* @param timestamp current tick time
*/
update(timestamp) {
if (this.running && (this.lastUpdate + CONNECTION_TIMEOUT) < timestamp) {
this.disconnect('timeout');
return;
}
if (this.recvQueue.length) {
const messages = this.recvQueue.splice(0, 4);
for (const message of messages) {
this.receiveOnline(message);
}
}
// if (this.sendQ.length) {
// const messages = this.sendQ.splice(0, 1)
// for (const message of messages) {
// this.sendPacket(message)
// }
// }
// Send ACKs
if (this.ackQueue.length > 0) {
const pk = new ACK_1.default();
pk.packets = this.ackQueue;
pk.encode();
this.sendPacket(pk);
this.ackQueue = [];
}
// Send NACKs
if (this.nackQueue.size) {
const pk = new NACK_1.default();
pk.packets = [...this.nackQueue];
pk.encode();
this.outLog('Sending NACK batch', this.nackQueue);
this.sendPacket(pk);
for (const nak of this.nackQueue) {
this.nacking.add(nak);
}
this.nackQueue.clear();
}
// TODO: Resend packets where we don't get an ACK. Not a big deal since
// we expect a NACK back anyways.
this.sendQueue();
}
disconnect(reason = 'unknown') {
this.state = 2 /* Disconnecting */;
this.listener.close(reason);
}
recieve(buffer) {
this.recvQueue.push(buffer);
this.lastUpdate = Date.now();
}
/**
* Recieve online RakNet packets
*/
receiveOnline(buffer) {
const header = buffer.readUInt8();
if ((header & bitflags_1.default.Valid) == 0) {
// Don't handle offline packets
return;
}
else if (header & bitflags_1.default.Ack) {
return this.handleACK(buffer);
}
else if (header & bitflags_1.default.Nack) {
return this.handleNACK(buffer);
}
else {
return this.handleDatagram(buffer);
}
}
handleACK(buffer) {
let packet = new ACK_1.default();
packet.buffer = buffer;
packet.decode();
for (let seq of packet.packets) {
if (this.recoveryList.has(seq)) {
// Calc ping maybe
this.recoveryList.delete(seq);
}
}
}
handleNACK(buffer) {
let packet = new NACK_1.default();
packet.buffer = buffer;
packet.decode();
this.inLog('[raknet] -> NACK', packet.packets);
for (let seq of packet.packets) {
if (this.recoveryList.has(seq)) {
let pk = this.recoveryList.get(seq);
// pk.sendTime = Date.now()
this.outLog('[raknet] resending NACK', pk);
// this.sendPacket(pk)
this.listener.sendBuffer(pk.buffer, this.address);
this.outLog('[raknet] sent', pk);
this.recoveryList.delete(seq);
}
else {
// This is bad. The connection will probably now die if encryption is enabled.
this.inLog('** LOST PACKET', seq);
}
}
}
handleDatagram(buffer) {
const dataPacket = DataPacket_1.DataPacket.from(buffer);
console.assert(dataPacket.sequenceNumber != null, 'Packet sequence number cannot be null');
// this.inLog('Reading datagram', buffer, dataPacket)
this.ackQueue.push(dataPacket.sequenceNumber);
this.receiveWindow.set(dataPacket.sequenceNumber, dataPacket);
const [missing, have] = this.receiveWindow.read();
// this.inLog('Reading', missing, have)
for (const miss of missing) {
if (!this.nacking.has(miss))
this.nackQueue.add(miss);
}
if (this.nackQueue.size)
this.inLog('NACKs while reading datagram', this.nackQueue, this.nacking);
for (const datagram of have) {
if (this.nacking.has(datagram.sequenceNumber) || this.nackQueue.has(datagram.sequenceNumber)) {
this.inLog('Recieved lost #', datagram.sequenceNumber);
this.nacking.delete(datagram.sequenceNumber);
this.nackQueue.delete(datagram.sequenceNumber);
}
for (const packet of datagram.packets) {
this.recievePacket(packet);
}
}
}
recievePacket(packet) {
// this.inLog('-> Encapsulated', packet, packet.isReliable())
if (packet.isReliable()) {
this.reliableReceiveWindow.set(packet.messageIndex, packet);
const readable = this.reliableReceiveWindow.read((lost) => debug('Lost ordered', lost, 'currently at', packet.messageIndex));
this.inLog('Reading reliable', readable);
for (const pak of readable) {
this.handlePacket(pak);
}
}
else {
this.handlePacket(packet);
}
}
handleSplit(packet) {
if (!this.splitPackets.has(packet.splitID)) {
this.splitPackets.set(packet.splitID, new Map([[packet.splitIndex, packet]]));
}
const splitPacket = this.splitPackets.get(packet.splitID);
splitPacket.set(packet.splitIndex, packet);
if (splitPacket.size === packet.splitCount) {
const bufs = [];
for (let i = 0; i < packet.splitCount; i++) {
bufs.push(splitPacket.get(i).buffer);
}
const encapsulated = new EncapsulatedPacket_1.default();
encapsulated.buffer = Buffer.concat(bufs);
this.splitPackets.delete(packet.splitID);
this.handlePacket(encapsulated);
}
else {
// debug('Waiting for split', packet.messageIndex, packet.splitID, packet.splitIndex, '/' , packet.splitCount)
}
}
async handlePacket(packet) {
if (packet.split) {
this.inLog('reading split');
this.handleSplit(packet);
return;
}
const id = packet.buffer.readUInt8();
// this.inLog('--> Encapsulated, h id', id, this.state, Status.Connecting)
if (id < 0x80) {
if (this.state == 0 /* Connecting */) {
if (id === Identifiers_1.default.ConnectionRequest && this.listener.server) {
this.inLog('got connection request');
this.handleConnectionRequest(packet.buffer);
}
else if (id === Identifiers_1.default.NewIncomingConnection && this.listener.server) {
const pak = NewIncomingConnection_1.default.from(packet.buffer);
const serverAddress = this.listener.socket.address();
// debug('incoming connection', serverAddress, pak.address)
if (serverAddress.port === pak.address.port) {
this.state = 1 /* Connected */;
this.listener.emit('openConnection', this);
}
}
else if (id === Identifiers_1.default.ConnectionRequestAccepted && this.listener.client) {
await this.handleConnectionRequestAccepted(packet.buffer);
this.inLog('Connected!');
this.state = 1 /* Connected */;
this.listener.emit('connected', this);
}
}
else if (id === Identifiers_1.default.DisconnectNotification) {
this.disconnect('client disconnect');
}
else if (id === Identifiers_1.default.ConnectedPing) {
await this.handleConnectedPing(packet.buffer);
}
else if (id === Identifiers_1.default.ConnectedPong) {
await this.handleConnectedPong(packet.buffer);
}
}
else if (this.state === 1 /* Connected */) {
this.listener.emit('encapsulated', packet, this.address); // To fit in software needs later
}
}
async handleConnectionRequestAccepted(buffer) {
if (this.listener instanceof Server_1.Server)
return;
this.inLog('Connection accepted');
let dataPacket = new ConnectionRequestAccepted_1.default();
dataPacket.buffer = buffer;
dataPacket.decode();
const pk = new NewIncomingConnection_1.default();
pk.address = this.listener.address;
pk.systemAddresses = [];
for (let i = 0; i < 20; i++) {
pk.systemAddresses.push(this.listener.address);
}
pk.requestTimestamp = dataPacket.requestTimestamp;
pk.acceptedTimestamp = dataPacket.acceptedTimestamp;
pk.encode();
let sendPacket = new EncapsulatedPacket_1.default();
sendPacket.reliability = 0;
sendPacket.buffer = pk.buffer;
this.addToQueue(sendPacket, Priority.Immediate);
}
async handleConnectionRequest(buffer) {
const dataPacket = ConnectionRequest_1.default.from(buffer);
let pk = new ConnectionRequestAccepted_1.default();
pk.clientAddress = this.address;
pk.requestTimestamp = dataPacket.requestTimestamp;
pk.acceptedTimestamp = BigInt(Date.now());
pk.encode();
let sendPacket = new EncapsulatedPacket_1.default();
sendPacket.reliability = 0;
sendPacket.buffer = pk.buffer;
this.addToQueue(sendPacket);
this.outLog('Sending connection accept', pk);
}
async handleConnectedPing(buffer) {
let dataPacket = new ConnectedPing_1.default();
dataPacket.buffer = buffer;
dataPacket.decode();
let pk = new ConnectedPong_1.default();
pk.clientTimestamp = dataPacket.clientTimestamp;
pk.serverTimestamp = BigInt(Date.now());
pk.encode();
let sendPacket = new EncapsulatedPacket_1.default();
sendPacket.reliability = 0;
sendPacket.buffer = pk.buffer;
this.addToQueue(sendPacket);
}
sendConnectedPing() {
const pk = new ConnectedPing_1.default();
pk.clientTimestamp = BigInt(Date.now());
pk.encode();
let sendPacket = new EncapsulatedPacket_1.default();
sendPacket.reliability = 0;
sendPacket.buffer = pk.buffer;
this.addToQueue(sendPacket, 1);
}
handleConnectedPong(buffer) {
if (this.listener instanceof Server_1.Server)
return;
const pk = ConnectedPong_1.default.from(buffer);
this.listener.lastPong = BigInt(pk.serverTimestamp || Date.now());
}
sendConnectionRequest(clientGUID, mtuSize) {
const packet = new ConnectionRequest_1.default();
// packet.mtuSize = mtuSize
packet.clientGUID = clientGUID;
packet.requestTimestamp = BigInt(Date.now());
packet.encode();
let sendPacket = new EncapsulatedPacket_1.default();
sendPacket.reliability = 0;
sendPacket.buffer = packet.buffer;
this.addToQueue(sendPacket, 1);
this.outLog('Sending connection request');
}
/**
* Sends an EncapsulatedPacket with a message index if ordered, and splits
* if needed.
* @param packet The packet to send
* @param flags Whether or not to queue this packet or send now
*/
addEncapsulatedToQueue(packet, flags = Priority.Normal) {
if (reliability_1.default.isReliable(packet.reliability)) {
packet.messageIndex = this.sendMessageIndex++;
if (packet.reliability == reliability_1.default.ReliableOrdered) {
packet.orderIndex = this.channelIndex[packet.orderChannel]++;
}
}
if (packet.getTotalLength() + 4 > this.mtuSize) {
let buffers = [], i = 0, splitIndex = 0;
while (i < packet.buffer.length) {
// Push format: [chunk index: int, chunk: buffer]
buffers.push([(splitIndex += 1) - 1, packet.buffer.slice(i, i += this.mtuSize - 60)]);
}
let splitID = ++this.sendSplitID % 65536;
for (let [count, buffer] of buffers) {
let pk = new EncapsulatedPacket_1.default();
pk.splitID = splitID;
pk.split = true;
pk.splitCount = buffers.length;
pk.reliability = packet.reliability;
pk.splitIndex = count;
pk.buffer = buffer;
if (count > 0) {
pk.messageIndex = this.sendMessageIndex++;
}
else {
pk.messageIndex = packet.messageIndex;
}
if (pk.reliability === 3) {
pk.orderChannel = packet.orderChannel;
pk.orderIndex = packet.orderIndex;
}
this.addToQueue(pk, flags | Priority.Immediate);
}
}
else {
this.addToQueue(packet, flags);
}
}
// Adds a packet to the queue
addToQueue(pk, flags = Priority.Normal) {
let priority = flags & 0b1;
if (priority === Priority.Immediate) {
const packet = new DataPacket_1.DataPacket();
packet.sequenceNumber = this.sendSequenceNumber++;
packet.packets.push(pk.toBinary());
packet.encode();
this.sendPacket(packet);
// packet.sendTime = Date.now()
this.recoveryList.set(packet.sequenceNumber, packet);
// this.outLog('Immedate Sent Q #', packet.sequenceNumber)
return;
}
const length = this.nextDatagram.length();
if (length + pk.getTotalLength() > this.mtuSize) {
this.sendQueue();
}
this.nextDatagram.packets.push(pk.toBinary());
}
sendQueue() {
if (this.nextDatagram.packets.length > 0) {
this.nextDatagram.sequenceNumber = this.sendSequenceNumber++;
this.nextDatagram.encode();
this.sendPacket(this.nextDatagram);
// this.sendQueue.sendTime = Date.now()
// console.log('Normal Sent Q #', this.nextDatagram.sequenceNumber)
this.recoveryList.set(this.nextDatagram.sequenceNumber, this.nextDatagram);
this.nextDatagram = new DataPacket_1.DataPacket();
}
}
sendPacket(packet) {
this.listener.sendBuffer(packet.buffer, this.address);
}
close() {
this.state = 3 /* Disconnected */;
const stream = new jsbinaryutils_1.default(Buffer.from('\x00\x00\x08\x15', 'binary'));
this.addEncapsulatedToQueue(EncapsulatedPacket_1.default.fromBinary(stream), Priority.Immediate); // Client discconect packet 0x15
}
}
exports.Connection = Connection;