Skip to content

Commit

Permalink
Fix Connection Timeouts + Typos
Browse files Browse the repository at this point in the history
  • Loading branch information
AnyBananaGAME committed Oct 30, 2024
1 parent 023af6f commit 3f33d93
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 63 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sanctumterra/raknet",
"version": "1.2.3",
"version": "1.2.4",
"description": "",
"main": "dist/index.js",
"scripts": {
Expand Down
File renamed without changes.
147 changes: 104 additions & 43 deletions src/client/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Emitter } from "@serenityjs/emitter";
import type { ClientEvents } from "./client-evens";
import type { ClientEvents } from "./client-events";
import { type RemoteInfo, type Socket, createSocket} from "node:dgram";
import { Framer } from "./framer";
import { Ack, Address, type Advertisement, ConnectionRequest, type Frame, fromString, OpenConnectionReplyOne, OpenConnectionReplyTwo, OpenConnectionRequestOne, OpenConnectionRequestTwo, Packet, Priority, UnconnectedPing, UnconnectedPong } from "../proto";
Expand All @@ -19,6 +19,8 @@ class Client extends Emitter<ClientEvents> {
private waitingForReplyTwo = false;
private waitingForReplyOne = false;

private isConnecting = false;

constructor(options: Partial<ClientOptions> = defaultClientOptions) {
super();
this.options = { ...defaultClientOptions, ...options };
Expand All @@ -40,12 +42,14 @@ class Client extends Emitter<ClientEvents> {

public async ping() : Promise<Advertisement | null> {
return new Promise((resolve) => {
const timeout = setTimeout(() => {
throw new Error("Failed to ping, timed out.");
}, this.options.timeout);

this.on("unconnected-pong", (packet) => {
clearTimeout(timeout);
resolve(fromString(packet.message));
});
setTimeout(() => {
resolve(null);
}, this.options.timeout);

const unconnectedPing = new UnconnectedPing();
unconnectedPing.guid = this.options.clientId;
Expand All @@ -55,35 +59,66 @@ class Client extends Emitter<ClientEvents> {
}

public async connect() : Promise<Advertisement> {
this.initSocket();
this.timer = setInterval(() => {
this.emit("tick");
}, 50);
const request = new OpenConnectionRequestOne();
request.mtu = this.options.mtuSize;
request.protocol = this.options.protocolVersion;
this.emit("open-connection-request-one", request);
const advertisement = await this.ping();
return new Promise((resolve, reject) => {
if(this.timeout) clearInterval(this.timeout);
this.timeout = setTimeout(() => {
this.removeAll();
this.socket.removeAllListeners();
this.socket.close();
clearInterval(this.timer);
clearInterval(this.timeout);
reject(new Error("Failed to connect, timed out."));
}, this.options.timeout);
this.once("ack", (packet) => {
if(advertisement) {
clearInterval(this.timeout);
this.emit("connect");
resolve(advertisement);
}
if (this.isConnecting) {
throw new Error("Connection attempt already in progress");
}

try {
this.isConnecting = true;
this.initSocket();
this.timer = setInterval(() => {
this.emit("tick");
}, 50);

const request = new OpenConnectionRequestOne();
request.mtu = this.options.mtuSize;
request.protocol = this.options.protocolVersion;

let connectionAttempts = 0;
const maxAttempts = 3;
let isResolved = false;

const advertisement = await this.ping();

return new Promise((resolve, reject) => {
const attemptConnection = () => {
if (isResolved) return;

if (connectionAttempts >= maxAttempts) {
isResolved = true;
this.cleanup();
reject(new Error("Connection timed out"));
return;
}

connectionAttempts++;
if(this.options.debug) Logger.debug(`Sending OpenConnectionRequestOne attempt ${connectionAttempts}/${maxAttempts}`);
this.emit("open-connection-request-one", request);
this.send(request.serialize());

setTimeout(attemptConnection, 2000);
};

this.onceAfter("new-incoming-connection", (packet) => {
if(advertisement && !isResolved) {
isResolved = true;
this.emit("connect");
this.isConnecting = false;
resolve(advertisement);
}
});

this.once("open-connection-reply-one", () => {
if(this.options.debug) Logger.debug("Received OpenConnectionReplyOne");
isResolved = true;
});

attemptConnection();
});
this.waitForReply1();
this.send(request.serialize());
});
} catch (error) {
this.isConnecting = false;
throw error;
}
}

public sendFrame(frame: Frame, priority: Priority): void {
Expand All @@ -101,7 +136,8 @@ class Client extends Emitter<ClientEvents> {
const timeout = setTimeout(() => {
this.waitingForReplyTwo = false;
if(this.options.debug) Logger.debug("Failed to receive OpenConnectionReplyTwo");
this.connect();
console.error("Failed to receive OpenConnectionReplyTwo");
this.cleanup();
}, 500);
this.on("open-connection-reply-two", () => {
clearTimeout(timeout);
Expand Down Expand Up @@ -141,13 +177,34 @@ class Client extends Emitter<ClientEvents> {
case Packet.OpenConnectionReplyTwo: {
const packet = new OpenConnectionReplyTwo(msg).deserialize();
this.emit("open-connection-reply-two", packet);
this.options.mtuSize = packet.mtu;

const conReq = new ConnectionRequest();
conReq.clientGuid = this.options.clientId;
conReq.timestamp = BigInt(Date.now());
conReq.useSecurity = false;
this.options.mtuSize = packet.mtu;
this.emit("connection-request", conReq);
this.framer.frameAndSend(conReq.serialize(), Priority.Immediate);

let connectionAttempts = 0;
const maxAttempts = 5;
const connectionInterval = setInterval(() => {
if (connectionAttempts >= maxAttempts) {
clearInterval(connectionInterval);
this.cleanup();
this.emit("error", new Error("Connection request timed out"));
return;
}

if(this.options.debug) Logger.debug(`Sending ConnectionRequest attempt ${connectionAttempts + 1}/${maxAttempts}`);
this.emit("connection-request", conReq);
this.framer.frameAndSend(conReq.serialize(), Priority.Immediate);
connectionAttempts++;
}, 1000);

this.once("new-incoming-connection", () => {
if(this.options.debug) Logger.debug("Received new incoming connection, clearing connection request interval");
clearInterval(connectionInterval);
});

break;
}
case Packet.FrameSet: {
Expand All @@ -165,15 +222,19 @@ class Client extends Emitter<ClientEvents> {
private waitForReply1() {
if(this.waitingForReplyOne) return;
this.waitingForReplyOne = true;
const timeout = setTimeout(() => {
this.waitingForReplyOne = false;
if(this.options.debug) Logger.debug("Failed to receive OpenConnectionReplyOne");
this.connect();
clearTimeout(timeout);
}, 500);

this.once("open-connection-reply-one", () => {
clearTimeout(timeout);
if(this.options.debug) Logger.debug("Received OpenConnectionReplyOne");
this.waitingForReplyOne = false;
});
}

private cleanup(): void {
this.removeAll();
this.socket.removeAllListeners();
this.socket.close();
clearInterval(this.timer);
clearTimeout(this.timeout);
this.isConnecting = false;
}
}
4 changes: 3 additions & 1 deletion src/client/framer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Packet,
Priority,
Reliability,
SystemAddress,
} from "../proto";
import { Frameset } from "../proto";
import { Logger } from "../utils";
Expand Down Expand Up @@ -72,12 +73,13 @@ export class Framer {
frame.payload,
).deserialize();
const newI = new NewIncomingConnection();
SystemAddress.count = 20;
newI.serverAddress = this.client.serverAddress;
newI.internalAddresses = packet.systemAddresses;
newI.incomingTimestamp = BigInt(Date.now());
newI.serverTimestamp = packet.timestamp;
this.client.emit("new-incoming-connection", newI);
this.frameAndSend(newI.serialize(), Priority.Immediate);
SystemAddress.count = 0;
break;
}
case 254: {
Expand Down
24 changes: 14 additions & 10 deletions src/proto/packets/types/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,20 @@ export class Address extends DataType {
const address = bytes.map((byte) => (byte ^ 0xff).toString()).join(".");
const port = stream.readUShort();
return new Address(address, port, version);
// biome-ignore lint/style/noUselessElse: <explanation>
} else if (version === 6) {
stream.skip(2);
const port = stream.readUShort();
stream.skip(4);
const addressParts = [];
for (let i = 0; i < 8; i++) {
const part = stream.readUShort() ^ 0xffff;
addressParts.push(part.toString(16).padStart(4, "0"));
}
const address = addressParts.join(":");
stream.skip(4);
return new Address(address, port, version);
}
const port = stream.readUShort();
stream.readUint32();
const addressParts = [];
for (let i = 0; i < 8; i++) {
const part = stream.readUShort() ^ 0xffff;
addressParts.push(part.toString(16).padStart(4, "0"));
}
const address = addressParts.join(":");
stream.readUint32();
return new Address(address, port, version);
return new Address("0.0.0.0", 0, 0);
}
}
20 changes: 14 additions & 6 deletions src/proto/packets/types/sys-address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,35 @@ import { DataType } from "./data-type";
import type { BinaryStream } from "@serenityjs/binarystream";

export class SystemAddress extends DataType {
static count = 0;

public static fromIdentifier(identifier: RemoteInfo): Address {
return new Address(identifier.address, identifier.port, 4);
}

public static read(stream: BinaryStream): Array<Address> {
const addresses: Array<Address> = [];
for (let index = 0; index < 20; index++) {
const address = Address.read(stream);
addresses.push(address);
try {
for (let index = 0; index < 10; index++) {
const address = Address.read(stream);
addresses.push(address);
}
} catch (error) {
console.error('Error reading system addresses:', error);
}
return addresses;
}

public static write(stream: BinaryStream): void {
const addresses: Array<Address> = [
{ address: "127.0.0.1", port: 0, version: 4 },
new Address("127.0.0.1", 0, 4),
];
for (let index = 0; index < 20; index++) {
const count = SystemAddress.count === 0 ? 10 : SystemAddress.count;

for (let index = 0; index < count; index++) {
Address.write(
stream,
addresses[index] || { address: "0.0.0.0", port: 0, version: 4 },
addresses[index] || new Address("0.0.0.0", 0, 4),
);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/tests/test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import "reflect-metadata";
import { Client } from "../client/client";

const client = new Client({ address: "127.0.0.1", port: 19133, protocolVersion: 11, debug: true });
const client = new Client({ address: "hivebedrock.network", port: 19132, protocolVersion: 11, debug: true });

client.on("connect", () => {
console.log("Connected to server");
})
});
console.time("connect");
client.connect().then((advertisement) => {
console.log(advertisement);
console.timeEnd("connect");
}).catch((err) => {
console.error(err);
});

0 comments on commit 3f33d93

Please sign in to comment.