Files
node-metaverse/lib/classes/Circuit.ts

614 lines
21 KiB
TypeScript
Raw Normal View History

import { UUID } from './UUID';
2017-11-26 01:14:02 +00:00
import * as dgram from 'dgram';
import type { Socket } from 'dgram';
import { Packet } from './Packet';
import type { MessageBase } from './MessageBase';
import { PacketAckMessage } from './messages/PacketAck';
import { Message } from '../enums/Message';
import type { StartPingCheckMessage } from './messages/StartPingCheck';
import { CompletePingCheckMessage } from './messages/CompletePingCheck';
import type { Subscription } from 'rxjs';
2021-09-22 15:34:53 +01:00
import { filter } from 'rxjs/operators'
import { FilterResponse } from '../enums/FilterResponse';
2021-09-22 15:34:53 +01:00
import { Subject } from 'rxjs';
import { TimeoutError } from './TimeoutError';
import { RequestXferMessage } from './messages/RequestXfer';
import { SendXferPacketMessage } from './messages/SendXferPacket';
import { ConfirmXferPacketMessage } from './messages/ConfirmXferPacket';
import type { AbortXferMessage } from './messages/AbortXfer';
import { PacketFlags } from '../enums/PacketFlags';
import type { AssetType } from '../enums/AssetType';
import { Utils } from './Utils';
import type * as Long from 'long';
2017-11-24 01:00:56 +00:00
export class Circuit
{
public secureSessionID: UUID;
public sessionID: UUID;
public circuitCode: number;
public udpBlacklist: string[];
public timestamp: number;
public port: number;
public ipAddress: string;
private client: Socket | null = null;
private sequenceNumber = 0;
private readonly awaitingAck = new Map<number, {
packet: Packet,
timeout: NodeJS.Timeout,
sent: number
}>();
private readonly receivedPackets = new Map<number, NodeJS.Timeout>();
private active = false;
private readonly onPacketReceived: Subject<Packet>;
private readonly onAckReceived: Subject<number>;
public constructor()
{
2017-11-26 01:14:02 +00:00
this.onPacketReceived = new Subject<Packet>();
this.onAckReceived = new Subject<number>();
2017-11-26 01:14:02 +00:00
}
public subscribeToMessages(ids: number[], callback: (packet: Packet) => Promise<void> | void): Subscription
{
const lookupObject: Record<number, boolean> = {};
2020-11-19 16:27:29 +00:00
for (const id of ids)
{
lookupObject[id] = true;
2020-11-19 16:27:29 +00:00
}
2021-09-22 15:34:53 +01:00
return this.onPacketReceived.pipe(filter((packet: Packet): boolean =>
{
2021-09-22 15:34:53 +01:00
return lookupObject[packet.message.id];
}) as any).subscribe(callback as any);
}
public sendMessage(message: MessageBase, flags: PacketFlags): number
2017-11-26 01:14:02 +00:00
{
if (!this.active)
{
throw new Error('Attempting to send a message on a closed circuit');
}
2017-11-26 01:14:02 +00:00
const packet: Packet = new Packet();
packet.message = message;
packet.sequenceNumber = this.sequenceNumber++;
packet.packetFlags = flags;
this.sendPacket(packet);
return packet.sequenceNumber;
2017-11-26 01:14:02 +00:00
}
public async XferFileUp(xferID: Long, data: Buffer): Promise<void>
{
return new Promise<void>((resolve, reject) =>
{
let packetID = 0;
const pos = {
position: 0
};
const subs = this.subscribeToMessages([
Message.AbortXfer,
Message.ConfirmXferPacket
], (packet: Packet) =>
{
switch (packet.message.id)
{
case Message.ConfirmXferPacket:
{
const msg = packet.message as ConfirmXferPacketMessage;
if (msg.XferID.ID.equals(xferID))
{
if (pos.position > -1)
{
packetID++;
this.sendXferPacket(xferID, packetID, data, pos);
}
}
break;
}
case Message.AbortXfer:
{
const msg = packet.message as AbortXferMessage;
if (msg.XferID.ID.equals(xferID))
{
subs.unsubscribe();
reject(new Error('Transfer aborted'));
}
break;
}
default:
break;
}
});
this.sendXferPacket(xferID, packetID, data, pos);
if (pos.position === -1)
{
subs.unsubscribe();
resolve();
}
});
}
public async XferFileDown(fileName: string, deleteOnCompletion: boolean, useBigPackets: boolean, vFileID: UUID, vFileType: AssetType, fromCache: boolean): Promise<Buffer>
- Add "GET" method to Caps - New events: ObjectPhysicsDataEvent, ParcelPropertiesEvent, NewObjectEvent, ObjectUpdateEvent, ObjectKilledEvent - Added getXML function to Color4, Vector2, Vector3, Vector4, GameObject, Region, Quaternion, UUID for opensim-compatible XML export - Added TextureAnim and ParticleSystem decoding to the "full" ObjectStore - Object store will automatically request missing "parent" prims - "setPersist" - When persist is TRUE, the ObjectStore will not forget about "killed" prims - useful for region scanning - Support for Flexible params, Light params, LightImage params, Mesh data, Sculpt maps - Fixed object scale being incorrectly calculated - Add terrain decoding (this was a ballache) - Add parcel map decoding - Add support for region windlight settings (region.environment) - Add support for materials (normal / specular maps) - Add getBuffer, getLong and bitwiseOr to UUID - Added a circular-reference-safe JSONStringify to Utils - Add XferFile capability to Circuit PUBLIC API: AssetCommands: - Rework "downloadAsset" to detect failures - NEW: downloadInventoryAsset() - uses TransferRequest for prim inventory items - NEW: getMaterials() - resolves material UUIDs RegionCommands: - NEW: getTerrainTextures() - NEW: exportSettings() - OpenSim XML export of region settings - NEW: async getTerrain() - Get binary terrain heightmap, 256x256 float32 - resolveObjects() - now fetches task inventory contents too. - resolveObjects() - fix calculation of land impact - NEW: getObjectByLocalID(localID: number, timeout: number) - NEW: getObjectByUUID(uuid: UUID, timeout: number) - NEW: getParcels(); - NEW: pruneObjects - removes missing GameObjects from a list - NEW: setPersist - prevent objectstore from forgetting about killed gameobjects
2018-10-31 11:28:24 +00:00
{
return new Promise<Buffer>((resolve, reject) =>
{
let subscription: null | Subscription = null;
2024-08-17 16:16:43 +01:00
let timeout: NodeJS.Timeout | null = null;
const receivedChunks: Record<number, Buffer> = {};
const resetTimeout = function(): void
- Add "GET" method to Caps - New events: ObjectPhysicsDataEvent, ParcelPropertiesEvent, NewObjectEvent, ObjectUpdateEvent, ObjectKilledEvent - Added getXML function to Color4, Vector2, Vector3, Vector4, GameObject, Region, Quaternion, UUID for opensim-compatible XML export - Added TextureAnim and ParticleSystem decoding to the "full" ObjectStore - Object store will automatically request missing "parent" prims - "setPersist" - When persist is TRUE, the ObjectStore will not forget about "killed" prims - useful for region scanning - Support for Flexible params, Light params, LightImage params, Mesh data, Sculpt maps - Fixed object scale being incorrectly calculated - Add terrain decoding (this was a ballache) - Add parcel map decoding - Add support for region windlight settings (region.environment) - Add support for materials (normal / specular maps) - Add getBuffer, getLong and bitwiseOr to UUID - Added a circular-reference-safe JSONStringify to Utils - Add XferFile capability to Circuit PUBLIC API: AssetCommands: - Rework "downloadAsset" to detect failures - NEW: downloadInventoryAsset() - uses TransferRequest for prim inventory items - NEW: getMaterials() - resolves material UUIDs RegionCommands: - NEW: getTerrainTextures() - NEW: exportSettings() - OpenSim XML export of region settings - NEW: async getTerrain() - Get binary terrain heightmap, 256x256 float32 - resolveObjects() - now fetches task inventory contents too. - resolveObjects() - fix calculation of land impact - NEW: getObjectByLocalID(localID: number, timeout: number) - NEW: getObjectByUUID(uuid: UUID, timeout: number) - NEW: getParcels(); - NEW: pruneObjects - removes missing GameObjects from a list - NEW: setPersist - prevent objectstore from forgetting about killed gameobjects
2018-10-31 11:28:24 +00:00
{
if (timeout !== null)
{
clearTimeout(timeout);
}
timeout = setTimeout(() =>
{
if (subscription !== null)
{
subscription.unsubscribe();
}
reject(new Error('Xfer Timeout'));
}, 10000);
};
resetTimeout();
const xferRequest = new RequestXferMessage();
const transferID = UUID.random().getLong();
xferRequest.XferID = {
ID: transferID,
Filename: Utils.StringToBuffer(fileName),
FilePath: (fromCache) ? 4 : 0,
DeleteOnCompletion: deleteOnCompletion,
UseBigPackets: useBigPackets,
VFileID: vFileID,
VFileType: vFileType
};
this.sendMessage(xferRequest, PacketFlags.Reliable);
let finished = false;
let finishID = 0;
let firstPacket = true;
let dataSize = 0;
- Add "GET" method to Caps - New events: ObjectPhysicsDataEvent, ParcelPropertiesEvent, NewObjectEvent, ObjectUpdateEvent, ObjectKilledEvent - Added getXML function to Color4, Vector2, Vector3, Vector4, GameObject, Region, Quaternion, UUID for opensim-compatible XML export - Added TextureAnim and ParticleSystem decoding to the "full" ObjectStore - Object store will automatically request missing "parent" prims - "setPersist" - When persist is TRUE, the ObjectStore will not forget about "killed" prims - useful for region scanning - Support for Flexible params, Light params, LightImage params, Mesh data, Sculpt maps - Fixed object scale being incorrectly calculated - Add terrain decoding (this was a ballache) - Add parcel map decoding - Add support for region windlight settings (region.environment) - Add support for materials (normal / specular maps) - Add getBuffer, getLong and bitwiseOr to UUID - Added a circular-reference-safe JSONStringify to Utils - Add XferFile capability to Circuit PUBLIC API: AssetCommands: - Rework "downloadAsset" to detect failures - NEW: downloadInventoryAsset() - uses TransferRequest for prim inventory items - NEW: getMaterials() - resolves material UUIDs RegionCommands: - NEW: getTerrainTextures() - NEW: exportSettings() - OpenSim XML export of region settings - NEW: async getTerrain() - Get binary terrain heightmap, 256x256 float32 - resolveObjects() - now fetches task inventory contents too. - resolveObjects() - fix calculation of land impact - NEW: getObjectByLocalID(localID: number, timeout: number) - NEW: getObjectByUUID(uuid: UUID, timeout: number) - NEW: getParcels(); - NEW: pruneObjects - removes missing GameObjects from a list - NEW: setPersist - prevent objectstore from forgetting about killed gameobjects
2018-10-31 11:28:24 +00:00
subscription = this.subscribeToMessages([
Message.SendXferPacket,
Message.AbortXfer
], (packet: Packet) =>
{
switch (packet.message.id)
{
case Message.AbortXfer:
{
const message = packet.message as AbortXferMessage;
if (message.XferID.ID.compare(transferID) === 0)
{
if (timeout !== null)
{
clearTimeout(timeout);
}
if (subscription !== null)
{
subscription.unsubscribe();
}
reject(new Error('Xfer Aborted'));
}
break;
}
case Message.SendXferPacket:
{
const message = packet.message as SendXferPacketMessage;
if (message.XferID.ID.compare(transferID) === 0)
{
resetTimeout();
const packetNum = message.XferID.Packet & 0x7FFFFFFF;
const finishedNow = message.XferID.Packet & 0x80000000;
if (firstPacket)
{
dataSize = message.DataPacket.Data.readUInt32LE(0);
receivedChunks[packetNum] = message.DataPacket.Data.subarray(4);
firstPacket = false;
}
else
{
receivedChunks[packetNum] = message.DataPacket.Data;
}
- Add "GET" method to Caps - New events: ObjectPhysicsDataEvent, ParcelPropertiesEvent, NewObjectEvent, ObjectUpdateEvent, ObjectKilledEvent - Added getXML function to Color4, Vector2, Vector3, Vector4, GameObject, Region, Quaternion, UUID for opensim-compatible XML export - Added TextureAnim and ParticleSystem decoding to the "full" ObjectStore - Object store will automatically request missing "parent" prims - "setPersist" - When persist is TRUE, the ObjectStore will not forget about "killed" prims - useful for region scanning - Support for Flexible params, Light params, LightImage params, Mesh data, Sculpt maps - Fixed object scale being incorrectly calculated - Add terrain decoding (this was a ballache) - Add parcel map decoding - Add support for region windlight settings (region.environment) - Add support for materials (normal / specular maps) - Add getBuffer, getLong and bitwiseOr to UUID - Added a circular-reference-safe JSONStringify to Utils - Add XferFile capability to Circuit PUBLIC API: AssetCommands: - Rework "downloadAsset" to detect failures - NEW: downloadInventoryAsset() - uses TransferRequest for prim inventory items - NEW: getMaterials() - resolves material UUIDs RegionCommands: - NEW: getTerrainTextures() - NEW: exportSettings() - OpenSim XML export of region settings - NEW: async getTerrain() - Get binary terrain heightmap, 256x256 float32 - resolveObjects() - now fetches task inventory contents too. - resolveObjects() - fix calculation of land impact - NEW: getObjectByLocalID(localID: number, timeout: number) - NEW: getObjectByUUID(uuid: UUID, timeout: number) - NEW: getParcels(); - NEW: pruneObjects - removes missing GameObjects from a list - NEW: setPersist - prevent objectstore from forgetting about killed gameobjects
2018-10-31 11:28:24 +00:00
const confirm = new ConfirmXferPacketMessage();
confirm.XferID = {
ID: transferID,
Packet: packetNum
};
this.sendMessage(confirm, PacketFlags.Reliable);
if (finishedNow)
{
finished = true;
finishID = packetNum;
}
if (finished)
{
// Check if we have all the pieces
for (let x = 0; x <= finishID; x++)
{
if (!receivedChunks[x])
{
return;
}
}
const conc: Buffer[] = [];
for (let x = 0; x <= finishID; x++)
{
conc.push(receivedChunks[x]);
}
if (timeout !== null)
{
clearTimeout(timeout);
}
if (subscription !== null)
{
subscription.unsubscribe();
}
const buf = Buffer.concat(conc);
if (buf.length !== dataSize)
{
console.warn('Warning: Received data size does not match expected');
}
resolve(buf);
- Add "GET" method to Caps - New events: ObjectPhysicsDataEvent, ParcelPropertiesEvent, NewObjectEvent, ObjectUpdateEvent, ObjectKilledEvent - Added getXML function to Color4, Vector2, Vector3, Vector4, GameObject, Region, Quaternion, UUID for opensim-compatible XML export - Added TextureAnim and ParticleSystem decoding to the "full" ObjectStore - Object store will automatically request missing "parent" prims - "setPersist" - When persist is TRUE, the ObjectStore will not forget about "killed" prims - useful for region scanning - Support for Flexible params, Light params, LightImage params, Mesh data, Sculpt maps - Fixed object scale being incorrectly calculated - Add terrain decoding (this was a ballache) - Add parcel map decoding - Add support for region windlight settings (region.environment) - Add support for materials (normal / specular maps) - Add getBuffer, getLong and bitwiseOr to UUID - Added a circular-reference-safe JSONStringify to Utils - Add XferFile capability to Circuit PUBLIC API: AssetCommands: - Rework "downloadAsset" to detect failures - NEW: downloadInventoryAsset() - uses TransferRequest for prim inventory items - NEW: getMaterials() - resolves material UUIDs RegionCommands: - NEW: getTerrainTextures() - NEW: exportSettings() - OpenSim XML export of region settings - NEW: async getTerrain() - Get binary terrain heightmap, 256x256 float32 - resolveObjects() - now fetches task inventory contents too. - resolveObjects() - fix calculation of land impact - NEW: getObjectByLocalID(localID: number, timeout: number) - NEW: getObjectByUUID(uuid: UUID, timeout: number) - NEW: getParcels(); - NEW: pruneObjects - removes missing GameObjects from a list - NEW: setPersist - prevent objectstore from forgetting about killed gameobjects
2018-10-31 11:28:24 +00:00
}
}
break;
}
default:
break;
- Add "GET" method to Caps - New events: ObjectPhysicsDataEvent, ParcelPropertiesEvent, NewObjectEvent, ObjectUpdateEvent, ObjectKilledEvent - Added getXML function to Color4, Vector2, Vector3, Vector4, GameObject, Region, Quaternion, UUID for opensim-compatible XML export - Added TextureAnim and ParticleSystem decoding to the "full" ObjectStore - Object store will automatically request missing "parent" prims - "setPersist" - When persist is TRUE, the ObjectStore will not forget about "killed" prims - useful for region scanning - Support for Flexible params, Light params, LightImage params, Mesh data, Sculpt maps - Fixed object scale being incorrectly calculated - Add terrain decoding (this was a ballache) - Add parcel map decoding - Add support for region windlight settings (region.environment) - Add support for materials (normal / specular maps) - Add getBuffer, getLong and bitwiseOr to UUID - Added a circular-reference-safe JSONStringify to Utils - Add XferFile capability to Circuit PUBLIC API: AssetCommands: - Rework "downloadAsset" to detect failures - NEW: downloadInventoryAsset() - uses TransferRequest for prim inventory items - NEW: getMaterials() - resolves material UUIDs RegionCommands: - NEW: getTerrainTextures() - NEW: exportSettings() - OpenSim XML export of region settings - NEW: async getTerrain() - Get binary terrain heightmap, 256x256 float32 - resolveObjects() - now fetches task inventory contents too. - resolveObjects() - fix calculation of land impact - NEW: getObjectByLocalID(localID: number, timeout: number) - NEW: getObjectByUUID(uuid: UUID, timeout: number) - NEW: getParcels(); - NEW: pruneObjects - removes missing GameObjects from a list - NEW: setPersist - prevent objectstore from forgetting about killed gameobjects
2018-10-31 11:28:24 +00:00
}
});
});
}
public async waitForAck(ack: number, timeout: number): Promise<void>
{
return new Promise<void>((resolve, reject) =>
{
const handleObj: {
2024-08-17 16:16:43 +01:00
timeout: NodeJS.Timeout | null,
subscription: Subscription | null
} = {
timeout: null,
subscription: null
};
handleObj.timeout = setTimeout(() =>
{
if (handleObj.subscription !== null)
{
handleObj.subscription.unsubscribe();
reject(new Error('Timeout'));
}
}, timeout);
handleObj.subscription = this.onAckReceived.subscribe((sequenceNumber: number) =>
{
if (sequenceNumber === ack)
{
if (handleObj.timeout !== null)
{
clearTimeout(handleObj.timeout);
handleObj.timeout = null;
}
if (handleObj.subscription !== null)
{
handleObj.subscription.unsubscribe();
handleObj.subscription = null;
}
resolve();
}
});
});
}
public init(): void
{
if (this.client !== null)
{
this.client.close();
}
this.client = dgram.createSocket('udp4');
this.client.on('message', (message, remote) =>
{
if (remote.address === this.ipAddress)
{
this.receivedPacket(message);
}
});
this.active = true;
}
public shutdown(): void
{
for (const seqKey of this.awaitingAck.keys())
{
const ack = this.awaitingAck.get(seqKey);
if (ack !== undefined)
{
clearTimeout(ack.timeout);
}
this.awaitingAck.delete(seqKey);
2020-11-19 16:27:29 +00:00
}
for (const seqKey of this.receivedPackets.keys())
{
const s = this.receivedPackets.get(seqKey);
if (s !== undefined)
{
clearTimeout(s);
}
this.receivedPackets.delete(seqKey);
2020-11-19 16:27:29 +00:00
}
if (this.client !== null)
{
this.client.close();
this.client = null;
this.onPacketReceived.complete();
this.onAckReceived.complete();
}
this.active = false;
}
public async sendAndWaitForMessage<T extends MessageBase>(message: MessageBase, flags: PacketFlags, id: Message, timeout: number, messageFilter?: (message: T) => FilterResponse): Promise<T>
{
const awaiter = this.waitForMessage(id, timeout, messageFilter);
this.sendMessage(message, flags);
return awaiter;
}
public async waitForMessage<T extends MessageBase>(id: Message, timeout: number, messageFilter?: (message: T) => FilterResponse): Promise<T>
{
return new Promise<T>((resolve, reject) =>
2017-11-26 01:14:02 +00:00
{
const handleObj: {
2024-08-17 16:16:43 +01:00
timeout: NodeJS.Timeout | null,
2017-11-26 01:14:02 +00:00
subscription: Subscription | null
} = {
timeout: null,
subscription: null
};
const timeoutFunc = (): void =>
2017-11-26 01:14:02 +00:00
{
if (handleObj.subscription !== null)
{
handleObj.subscription.unsubscribe();
const err = new TimeoutError('Timeout waiting for message of type ' + Message[id]);
err.timeout = true;
err.waitingForMessage = id;
reject(err);
2017-11-26 01:14:02 +00:00
}
};
handleObj.timeout = setTimeout(timeoutFunc, timeout);
2017-11-26 01:14:02 +00:00
handleObj.subscription = this.subscribeToMessages([id], (packet: Packet) =>
{
let finish = false;
if (packet.message.id === id)
2017-11-26 01:14:02 +00:00
{
2018-10-10 10:12:20 +01:00
if (messageFilter === undefined)
2017-11-26 01:14:02 +00:00
{
finish = true;
}
else
{
2018-10-10 10:12:20 +01:00
const filterResult = messageFilter(packet.message as T);
if (filterResult === FilterResponse.Finish)
2017-11-26 01:14:02 +00:00
{
finish = true;
2017-11-26 01:14:02 +00:00
}
else if (filterResult === FilterResponse.Match)
2017-11-26 01:14:02 +00:00
{
// Extend
if (handleObj.timeout !== null)
{
clearTimeout(handleObj.timeout);
}
handleObj.timeout = setTimeout(timeoutFunc, timeout);
2017-11-26 01:14:02 +00:00
}
}
}
if (finish)
{
if (handleObj.timeout !== null)
{
clearTimeout(handleObj.timeout);
handleObj.timeout = null;
}
if (handleObj.subscription !== null)
{
handleObj.subscription.unsubscribe();
handleObj.subscription = null;
}
resolve(packet.message as T);
}
});
2017-11-26 01:14:02 +00:00
});
}
public getOldestUnacked(): number
2017-11-26 01:14:02 +00:00
{
let result = 0;
let oldest = -1;
const keys: number[] = Array.from(this.awaitingAck.keys());
for (const nSeq of keys)
2017-11-26 01:14:02 +00:00
{
const awaiting = this.awaitingAck.get(nSeq);
if (awaiting !== undefined)
{
if (oldest === -1 || awaiting.sent < oldest)
2017-11-26 01:14:02 +00:00
{
result = nSeq;
oldest = awaiting.sent;
}
}
}
return result;
}
private sendXferPacket(xferID: Long, packetID: number, data: Buffer, pos: { position: number }): void
{
const sendXfer = new SendXferPacketMessage();
let final = false;
sendXfer.XferID = {
ID: xferID,
Packet: packetID
};
const packetLength = Math.min(data.length - pos.position, 1000);
if (packetLength < 1000)
{
sendXfer.XferID.Packet = (sendXfer.XferID.Packet | 0x80000000) >>> 0;
final = true;
}
if (packetID === 0)
{
const packet = Buffer.allocUnsafe(packetLength + 4);
packet.writeUInt32LE(data.length, 0);
data.copy(packet, 4, 0, packetLength);
sendXfer.DataPacket = {
Data: packet
};
pos.position += packetLength;
}
else
{
const packet = data.subarray(pos.position, pos.position + packetLength);
sendXfer.DataPacket = {
Data: packet
};
pos.position += packetLength;
}
this.sendMessage(sendXfer, PacketFlags.Reliable);
if (final)
{
pos.position = -1;
}
}
private resend(sequenceNumber: number): void
{
if (!this.active)
{
console.log('Resend triggered, but circuit is not active!');
return;
}
const waiting = this.awaitingAck.get(sequenceNumber);
if (waiting)
{
const toResend: Packet = waiting.packet;
toResend.packetFlags = toResend.packetFlags | PacketFlags.Resent;
this.sendPacket(toResend);
}
}
private sendPacket(packet: Packet): void
{
if (packet.packetFlags & PacketFlags.Reliable)
{
this.awaitingAck.set(packet.sequenceNumber,
{
packet: packet,
timeout: setTimeout(this.resend.bind(this, packet.sequenceNumber), 1000),
sent: new Date().getTime()
});
2017-11-26 01:14:02 +00:00
}
let dataToSend: Buffer = Buffer.allocUnsafe(packet.getSize());
dataToSend = packet.writeToBuffer(dataToSend, 0);
2017-11-26 01:14:02 +00:00
if (this.client !== null)
{
this.client.send(dataToSend, 0, dataToSend.length, this.port, this.ipAddress, (_err, _bytes) =>
2017-11-26 01:14:02 +00:00
{
// nothing
});
2017-11-26 01:14:02 +00:00
}
else
{
console.error('Attempted to send packet but UDP client is null');
}
}
private ackReceived(sequenceNumber: number): void
2017-11-26 01:14:02 +00:00
{
const awaiting = this.awaitingAck.get(sequenceNumber);
if (awaiting !== undefined)
{
clearTimeout(awaiting.timeout);
this.awaitingAck.delete(sequenceNumber);
}
this.onAckReceived.next(sequenceNumber);
}
private sendAck(sequenceNumber: number): void
2017-11-26 01:14:02 +00:00
{
const msg: PacketAckMessage = new PacketAckMessage();
msg.Packets = [
{
ID: sequenceNumber
2017-11-26 01:14:02 +00:00
}
];
2023-11-09 18:06:49 +00:00
this.sendMessage(msg, 0 as PacketFlags);
2017-11-26 01:14:02 +00:00
}
private expireReceivedPacket(sequenceNumber: number): void
{
// Enough time has elapsed that we can forget about this packet
this.receivedPackets.delete(sequenceNumber);
}
private receivedPacket(bytes: Buffer): void
2017-11-26 01:14:02 +00:00
{
const packet = new Packet();
2017-12-31 02:38:29 +00:00
try
{
packet.readFromBuffer(bytes, 0, this.ackReceived.bind(this), this.sendAck.bind(this));
}
catch (erro)
2017-12-31 02:38:29 +00:00
{
console.error(erro);
return;
}
const received = this.receivedPackets.get(packet.sequenceNumber);
if (received)
{
clearTimeout(received);
this.receivedPackets.set(packet.sequenceNumber, setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000));
this.sendAck(packet.sequenceNumber);
return;
}
this.receivedPackets.set(packet.sequenceNumber, setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000));
2018-10-10 10:12:20 +01:00
// console.log('<--- ' + packet.message.name);
2017-11-26 01:14:02 +00:00
if (packet.message.id === Message.PacketAck)
{
const msg = packet.message as PacketAckMessage;
2020-11-19 16:27:29 +00:00
for (const obj of msg.Packets)
2017-11-26 01:14:02 +00:00
{
this.ackReceived(obj.ID);
2020-11-19 16:27:29 +00:00
}
2017-11-26 01:14:02 +00:00
}
else if (packet.message.id === Message.StartPingCheck)
{
const msg = packet.message as StartPingCheckMessage;
const reply: CompletePingCheckMessage = new CompletePingCheckMessage();
reply.PingID = {
PingID: msg.PingID.PingID
};
2023-11-09 18:06:49 +00:00
this.sendMessage(reply, 0 as PacketFlags);
2017-11-26 01:14:02 +00:00
}
this.onPacketReceived.next(packet);
}
2017-11-24 01:00:56 +00:00
}