packet-events-js 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +358 -14
- package/package.json +2 -3
- package/src/advanced/ConnectionManager.js +486 -0
- package/src/advanced/MiddlewareSystem.js +416 -0
- package/src/advanced/PacketRecorder.js +336 -0
- package/src/advanced/ProtocolAnalyzer.js +396 -0
- package/src/advanced/index.js +32 -0
- package/src/auth/AuthHandler.js +246 -0
- package/src/index.js +3 -1
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { EventEmitter } from '../events/EventEmitter.js';
|
|
2
|
+
import { PacketBuffer } from '../utils/PacketBuffer.js';
|
|
3
|
+
|
|
4
|
+
export class ProtocolMetrics {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.reset();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
reset() {
|
|
10
|
+
this.startTime = Date.now();
|
|
11
|
+
this.packetsReceived = 0;
|
|
12
|
+
this.packetsSent = 0;
|
|
13
|
+
this.bytesReceived = 0;
|
|
14
|
+
this.bytesSent = 0;
|
|
15
|
+
this.compressionSaved = 0;
|
|
16
|
+
this.encryptedPackets = 0;
|
|
17
|
+
this.errors = 0;
|
|
18
|
+
this.latencyHistory = [];
|
|
19
|
+
this.throughputHistory = [];
|
|
20
|
+
this.packetTypeStats = new Map();
|
|
21
|
+
this.stateTransitions = [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
recordPacket(direction, size, packetName, compressed = false, originalSize = 0) {
|
|
25
|
+
if (direction === 'CLIENTBOUND') {
|
|
26
|
+
this.packetsReceived++;
|
|
27
|
+
this.bytesReceived += size;
|
|
28
|
+
} else {
|
|
29
|
+
this.packetsSent++;
|
|
30
|
+
this.bytesSent += size;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (compressed && originalSize > size) {
|
|
34
|
+
this.compressionSaved += (originalSize - size);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const stats = this.packetTypeStats.get(packetName) || { count: 0, bytes: 0 };
|
|
38
|
+
stats.count++;
|
|
39
|
+
stats.bytes += size;
|
|
40
|
+
this.packetTypeStats.set(packetName, stats);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
recordLatency(ms) {
|
|
44
|
+
this.latencyHistory.push({ time: Date.now(), latency: ms });
|
|
45
|
+
if (this.latencyHistory.length > 1000) {
|
|
46
|
+
this.latencyHistory.shift();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
recordStateTransition(from, to) {
|
|
51
|
+
this.stateTransitions.push({ time: Date.now(), from, to });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getAverageLatency() {
|
|
55
|
+
if (this.latencyHistory.length === 0) return 0;
|
|
56
|
+
return this.latencyHistory.reduce((a, b) => a + b.latency, 0) / this.latencyHistory.length;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getMinLatency() {
|
|
60
|
+
if (this.latencyHistory.length === 0) return 0;
|
|
61
|
+
return Math.min(...this.latencyHistory.map(l => l.latency));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getMaxLatency() {
|
|
65
|
+
if (this.latencyHistory.length === 0) return 0;
|
|
66
|
+
return Math.max(...this.latencyHistory.map(l => l.latency));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getThroughput() {
|
|
70
|
+
const elapsed = (Date.now() - this.startTime) / 1000;
|
|
71
|
+
return {
|
|
72
|
+
packetsPerSecond: ((this.packetsReceived + this.packetsSent) / elapsed).toFixed(2),
|
|
73
|
+
bytesPerSecond: ((this.bytesReceived + this.bytesSent) / elapsed).toFixed(2),
|
|
74
|
+
kbpsIn: ((this.bytesReceived / elapsed) / 1024 * 8).toFixed(2),
|
|
75
|
+
kbpsOut: ((this.bytesSent / elapsed) / 1024 * 8).toFixed(2)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getReport() {
|
|
80
|
+
const elapsed = (Date.now() - this.startTime) / 1000;
|
|
81
|
+
return {
|
|
82
|
+
duration: elapsed,
|
|
83
|
+
packets: {
|
|
84
|
+
received: this.packetsReceived,
|
|
85
|
+
sent: this.packetsSent,
|
|
86
|
+
total: this.packetsReceived + this.packetsSent
|
|
87
|
+
},
|
|
88
|
+
bytes: {
|
|
89
|
+
received: this.bytesReceived,
|
|
90
|
+
sent: this.bytesSent,
|
|
91
|
+
total: this.bytesReceived + this.bytesSent,
|
|
92
|
+
compressionSaved: this.compressionSaved
|
|
93
|
+
},
|
|
94
|
+
latency: {
|
|
95
|
+
average: this.getAverageLatency().toFixed(2),
|
|
96
|
+
min: this.getMinLatency(),
|
|
97
|
+
max: this.getMaxLatency(),
|
|
98
|
+
samples: this.latencyHistory.length
|
|
99
|
+
},
|
|
100
|
+
throughput: this.getThroughput(),
|
|
101
|
+
packetTypes: Object.fromEntries(this.packetTypeStats),
|
|
102
|
+
stateTransitions: this.stateTransitions,
|
|
103
|
+
errors: this.errors
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export class PacketInspector {
|
|
109
|
+
constructor() {
|
|
110
|
+
this.inspectors = new Map();
|
|
111
|
+
this.registerDefaultInspectors();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
registerDefaultInspectors() {
|
|
115
|
+
this.register('position', (packet) => {
|
|
116
|
+
const fields = ['x', 'y', 'z', 'yaw', 'pitch'];
|
|
117
|
+
const result = {};
|
|
118
|
+
for (const field of fields) {
|
|
119
|
+
if (packet[field] !== undefined) {
|
|
120
|
+
result[field] = packet[field];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (Object.keys(result).length > 0) {
|
|
124
|
+
result.blockPos = {
|
|
125
|
+
x: Math.floor(result.x || 0),
|
|
126
|
+
y: Math.floor(result.y || 0),
|
|
127
|
+
z: Math.floor(result.z || 0)
|
|
128
|
+
};
|
|
129
|
+
result.chunkPos = {
|
|
130
|
+
x: Math.floor((result.x || 0) / 16),
|
|
131
|
+
z: Math.floor((result.z || 0) / 16)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
this.register('entity', (packet) => {
|
|
138
|
+
const fields = ['entityId', 'entityUUID', 'entityType', 'velocityX', 'velocityY', 'velocityZ'];
|
|
139
|
+
const result = {};
|
|
140
|
+
for (const field of fields) {
|
|
141
|
+
if (packet[field] !== undefined) {
|
|
142
|
+
result[field] = packet[field];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
this.register('chat', (packet) => {
|
|
149
|
+
const result = {};
|
|
150
|
+
if (packet.message) result.message = packet.message;
|
|
151
|
+
if (packet.content) result.content = packet.content;
|
|
152
|
+
if (packet.sender) result.sender = packet.sender;
|
|
153
|
+
if (packet.type !== undefined) result.type = packet.type;
|
|
154
|
+
return result;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
register(name, inspector) {
|
|
159
|
+
this.inspectors.set(name, inspector);
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
inspect(packet, inspectorName = null) {
|
|
164
|
+
const results = {};
|
|
165
|
+
|
|
166
|
+
if (inspectorName) {
|
|
167
|
+
const inspector = this.inspectors.get(inspectorName);
|
|
168
|
+
if (inspector) {
|
|
169
|
+
results[inspectorName] = inspector(packet);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
for (const [name, inspector] of this.inspectors) {
|
|
173
|
+
const result = inspector(packet);
|
|
174
|
+
if (Object.keys(result).length > 0) {
|
|
175
|
+
results[name] = result;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return results;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
hexDump(buffer, bytesPerLine = 16) {
|
|
184
|
+
const lines = [];
|
|
185
|
+
for (let i = 0; i < buffer.length; i += bytesPerLine) {
|
|
186
|
+
const slice = buffer.subarray(i, Math.min(i + bytesPerLine, buffer.length));
|
|
187
|
+
const hex = [...slice].map(b => b.toString(16).padStart(2, '0')).join(' ');
|
|
188
|
+
const ascii = [...slice].map(b => b >= 32 && b <= 126 ? String.fromCharCode(b) : '.').join('');
|
|
189
|
+
lines.push(`${i.toString(16).padStart(8, '0')} ${hex.padEnd(bytesPerLine * 3 - 1)} |${ascii}|`);
|
|
190
|
+
}
|
|
191
|
+
return lines.join('\n');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
analyzeStructure(packet) {
|
|
195
|
+
const structure = {
|
|
196
|
+
className: packet.constructor.name,
|
|
197
|
+
packetId: packet.constructor.packetId,
|
|
198
|
+
packetName: packet.constructor.packetName,
|
|
199
|
+
fields: []
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
for (const [key, value] of Object.entries(packet)) {
|
|
203
|
+
if (key.startsWith('_')) continue;
|
|
204
|
+
|
|
205
|
+
let type = typeof value;
|
|
206
|
+
let details = {};
|
|
207
|
+
|
|
208
|
+
if (value === null) {
|
|
209
|
+
type = 'null';
|
|
210
|
+
} else if (value === undefined) {
|
|
211
|
+
type = 'undefined';
|
|
212
|
+
} else if (Buffer.isBuffer(value)) {
|
|
213
|
+
type = 'Buffer';
|
|
214
|
+
details.length = value.length;
|
|
215
|
+
} else if (Array.isArray(value)) {
|
|
216
|
+
type = 'Array';
|
|
217
|
+
details.length = value.length;
|
|
218
|
+
} else if (typeof value === 'bigint') {
|
|
219
|
+
type = 'BigInt';
|
|
220
|
+
details.value = value.toString();
|
|
221
|
+
} else if (value && typeof value === 'object') {
|
|
222
|
+
type = value.constructor.name;
|
|
223
|
+
if (value.toString) {
|
|
224
|
+
details.string = value.toString();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
structure.fields.push({ name: key, type, value: details.value || value, details });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return structure;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export class ProtocolAnalyzer extends EventEmitter {
|
|
236
|
+
constructor(options = {}) {
|
|
237
|
+
super();
|
|
238
|
+
|
|
239
|
+
this.metrics = new ProtocolMetrics();
|
|
240
|
+
this.inspector = new PacketInspector();
|
|
241
|
+
this.isAnalyzing = false;
|
|
242
|
+
this.filters = options.filters || [];
|
|
243
|
+
this.alerts = [];
|
|
244
|
+
this.alertRules = new Map();
|
|
245
|
+
|
|
246
|
+
this.setupDefaultAlerts();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setupDefaultAlerts() {
|
|
250
|
+
this.addAlertRule('high_latency', (metrics) => {
|
|
251
|
+
if (metrics.getAverageLatency() > 200) {
|
|
252
|
+
return { level: 'warning', message: `High latency: ${metrics.getAverageLatency().toFixed(0)}ms` };
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
this.addAlertRule('packet_flood', (metrics) => {
|
|
258
|
+
const throughput = metrics.getThroughput();
|
|
259
|
+
if (parseFloat(throughput.packetsPerSecond) > 1000) {
|
|
260
|
+
return { level: 'warning', message: `High packet rate: ${throughput.packetsPerSecond}/s` };
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
this.addAlertRule('error_rate', (metrics) => {
|
|
266
|
+
const total = metrics.packetsReceived + metrics.packetsSent;
|
|
267
|
+
if (total > 100 && (metrics.errors / total) > 0.01) {
|
|
268
|
+
return { level: 'error', message: `High error rate: ${(metrics.errors / total * 100).toFixed(2)}%` };
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
addAlertRule(name, rule) {
|
|
275
|
+
this.alertRules.set(name, rule);
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
removeAlertRule(name) {
|
|
280
|
+
this.alertRules.delete(name);
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
checkAlerts() {
|
|
285
|
+
for (const [name, rule] of this.alertRules) {
|
|
286
|
+
const alert = rule(this.metrics);
|
|
287
|
+
if (alert) {
|
|
288
|
+
const alertRecord = {
|
|
289
|
+
time: Date.now(),
|
|
290
|
+
rule: name,
|
|
291
|
+
...alert
|
|
292
|
+
};
|
|
293
|
+
this.alerts.push(alertRecord);
|
|
294
|
+
this.emit('alert', alertRecord);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
start() {
|
|
300
|
+
this.isAnalyzing = true;
|
|
301
|
+
this.metrics.reset();
|
|
302
|
+
this.alerts = [];
|
|
303
|
+
this.emit('start');
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
stop() {
|
|
308
|
+
this.isAnalyzing = false;
|
|
309
|
+
this.emit('stop', this.getReport());
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
analyzePacket(packet, direction, state, rawData) {
|
|
314
|
+
if (!this.isAnalyzing) return null;
|
|
315
|
+
|
|
316
|
+
const analysis = {
|
|
317
|
+
timestamp: Date.now(),
|
|
318
|
+
direction,
|
|
319
|
+
state: typeof state === 'object' ? state.name : state,
|
|
320
|
+
packetName: packet.constructor.packetName,
|
|
321
|
+
packetId: packet.constructor.packetId,
|
|
322
|
+
size: rawData.length,
|
|
323
|
+
inspection: this.inspector.inspect(packet),
|
|
324
|
+
structure: this.inspector.analyzeStructure(packet)
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
this.metrics.recordPacket(direction, rawData.length, packet.constructor.packetName);
|
|
328
|
+
|
|
329
|
+
if ((this.metrics.packetsReceived + this.metrics.packetsSent) % 100 === 0) {
|
|
330
|
+
this.checkAlerts();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
this.emit('packet', analysis);
|
|
334
|
+
return analysis;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
recordLatency(ms) {
|
|
338
|
+
this.metrics.recordLatency(ms);
|
|
339
|
+
this.emit('latency', ms);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
recordError(error) {
|
|
343
|
+
this.metrics.errors++;
|
|
344
|
+
this.emit('error', error);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
recordStateTransition(from, to) {
|
|
348
|
+
this.metrics.recordStateTransition(from, to);
|
|
349
|
+
this.emit('stateChange', { from, to });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
getReport() {
|
|
353
|
+
return {
|
|
354
|
+
...this.metrics.getReport(),
|
|
355
|
+
alerts: this.alerts,
|
|
356
|
+
isAnalyzing: this.isAnalyzing
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
getTopPackets(limit = 10) {
|
|
361
|
+
const sorted = [...this.metrics.packetTypeStats.entries()]
|
|
362
|
+
.sort((a, b) => b[1].count - a[1].count)
|
|
363
|
+
.slice(0, limit);
|
|
364
|
+
|
|
365
|
+
return sorted.map(([name, stats]) => ({
|
|
366
|
+
name,
|
|
367
|
+
count: stats.count,
|
|
368
|
+
bytes: stats.bytes,
|
|
369
|
+
avgSize: (stats.bytes / stats.count).toFixed(1)
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
generateTrafficGraph(width = 60) {
|
|
374
|
+
const history = this.metrics.latencyHistory.slice(-width);
|
|
375
|
+
if (history.length === 0) return 'No data';
|
|
376
|
+
|
|
377
|
+
const max = Math.max(...history.map(h => h.latency), 1);
|
|
378
|
+
const height = 10;
|
|
379
|
+
const lines = [];
|
|
380
|
+
|
|
381
|
+
for (let y = height; y >= 0; y--) {
|
|
382
|
+
let line = `${Math.round(max * y / height).toString().padStart(4)} |`;
|
|
383
|
+
for (const h of history) {
|
|
384
|
+
const barHeight = Math.round((h.latency / max) * height);
|
|
385
|
+
line += barHeight >= y ? '█' : ' ';
|
|
386
|
+
}
|
|
387
|
+
lines.push(line);
|
|
388
|
+
}
|
|
389
|
+
lines.push(' +' + '-'.repeat(width));
|
|
390
|
+
lines.push(' Latency over time (ms)');
|
|
391
|
+
|
|
392
|
+
return lines.join('\n');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export default { ProtocolAnalyzer, ProtocolMetrics, PacketInspector };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export {
|
|
2
|
+
PacketRecorder,
|
|
3
|
+
PacketReplayer,
|
|
4
|
+
PacketRecord
|
|
5
|
+
} from './PacketRecorder.js';
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
ProtocolAnalyzer,
|
|
9
|
+
ProtocolMetrics,
|
|
10
|
+
PacketInspector
|
|
11
|
+
} from './ProtocolAnalyzer.js';
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
MiddlewarePipeline,
|
|
15
|
+
MiddlewareContext,
|
|
16
|
+
Middleware,
|
|
17
|
+
LoggingMiddleware,
|
|
18
|
+
RateLimitMiddleware,
|
|
19
|
+
ValidationMiddleware,
|
|
20
|
+
TransformMiddleware,
|
|
21
|
+
CacheMiddleware,
|
|
22
|
+
MetricsMiddleware
|
|
23
|
+
} from './MiddlewareSystem.js';
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
AutoReconnect,
|
|
27
|
+
ReconnectStrategy,
|
|
28
|
+
HealthMonitor,
|
|
29
|
+
HealthStatus,
|
|
30
|
+
ConnectionPool,
|
|
31
|
+
CircuitBreaker
|
|
32
|
+
} from './ConnectionManager.js';
|
package/src/auth/AuthHandler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MinecraftEncryption, EncryptedConnection } from '../crypto/Encryption.js';
|
|
2
2
|
import { MojangAPI } from './MojangAPI.js';
|
|
3
3
|
import { UUID } from '../protocol/types/UUID.js';
|
|
4
|
+
import { createHmac } from 'crypto';
|
|
4
5
|
|
|
5
6
|
export const AuthMode = Object.freeze({
|
|
6
7
|
ONLINE: 'online',
|
|
@@ -9,11 +10,228 @@ export const AuthMode = Object.freeze({
|
|
|
9
10
|
VELOCITY: 'velocity'
|
|
10
11
|
});
|
|
11
12
|
|
|
13
|
+
export class BungeeCordForwarding {
|
|
14
|
+
static parse(serverAddress) {
|
|
15
|
+
const parts = serverAddress.split('\0');
|
|
16
|
+
|
|
17
|
+
if (parts.length < 3) {
|
|
18
|
+
return { success: false, reason: 'INVALID_FORMAT' };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const hostname = parts[0];
|
|
22
|
+
const clientIP = parts[1];
|
|
23
|
+
const uuidString = parts[2];
|
|
24
|
+
|
|
25
|
+
let properties = [];
|
|
26
|
+
if (parts.length > 3 && parts[3]) {
|
|
27
|
+
try {
|
|
28
|
+
properties = JSON.parse(parts[3]);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
properties = [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let uuid;
|
|
35
|
+
try {
|
|
36
|
+
uuid = UUID.fromString(uuidString);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return { success: false, reason: 'INVALID_UUID' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
success: true,
|
|
43
|
+
hostname,
|
|
44
|
+
clientIP,
|
|
45
|
+
uuid,
|
|
46
|
+
properties
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static create(hostname, clientIP, uuid, properties = []) {
|
|
51
|
+
const uuidString = uuid.toString(false);
|
|
52
|
+
const propsJson = properties.length > 0 ? JSON.stringify(properties) : '';
|
|
53
|
+
|
|
54
|
+
if (propsJson) {
|
|
55
|
+
return `${hostname}\0${clientIP}\0${uuidString}\0${propsJson}`;
|
|
56
|
+
}
|
|
57
|
+
return `${hostname}\0${clientIP}\0${uuidString}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class VelocityForwarding {
|
|
62
|
+
static MODERN_FORWARDING_VERSION = 1;
|
|
63
|
+
static MODERN_LAZY_SESSION = 2;
|
|
64
|
+
static MODERN_FORWARDING_WITH_KEY = 3;
|
|
65
|
+
static MODERN_FORWARDING_WITH_KEY_V2 = 4;
|
|
66
|
+
|
|
67
|
+
static verify(data, secret) {
|
|
68
|
+
if (!secret) {
|
|
69
|
+
return { success: false, reason: 'NO_SECRET' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (data.length < 32) {
|
|
73
|
+
return { success: false, reason: 'DATA_TOO_SHORT' };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const signature = data.subarray(0, 32);
|
|
77
|
+
const payload = data.subarray(32);
|
|
78
|
+
|
|
79
|
+
const expectedSignature = createHmac('sha256', secret)
|
|
80
|
+
.update(payload)
|
|
81
|
+
.digest();
|
|
82
|
+
|
|
83
|
+
if (!signature.equals(expectedSignature)) {
|
|
84
|
+
return { success: false, reason: 'INVALID_SIGNATURE' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return VelocityForwarding.parsePayload(payload);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static parsePayload(payload) {
|
|
91
|
+
try {
|
|
92
|
+
let offset = 0;
|
|
93
|
+
|
|
94
|
+
const version = payload.readInt32BE(offset);
|
|
95
|
+
offset += 4;
|
|
96
|
+
|
|
97
|
+
const ipLength = payload.readInt16BE(offset);
|
|
98
|
+
offset += 2;
|
|
99
|
+
const clientIP = payload.subarray(offset, offset + ipLength).toString('utf8');
|
|
100
|
+
offset += ipLength;
|
|
101
|
+
|
|
102
|
+
const uuidBytes = payload.subarray(offset, offset + 16);
|
|
103
|
+
offset += 16;
|
|
104
|
+
const uuid = UUID.fromBytes(uuidBytes);
|
|
105
|
+
|
|
106
|
+
const usernameLength = payload.readInt16BE(offset);
|
|
107
|
+
offset += 2;
|
|
108
|
+
const username = payload.subarray(offset, offset + usernameLength).toString('utf8');
|
|
109
|
+
offset += usernameLength;
|
|
110
|
+
|
|
111
|
+
let properties = [];
|
|
112
|
+
if (offset < payload.length) {
|
|
113
|
+
const propsCount = payload.readInt32BE(offset);
|
|
114
|
+
offset += 4;
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < propsCount; i++) {
|
|
117
|
+
const nameLen = payload.readInt16BE(offset);
|
|
118
|
+
offset += 2;
|
|
119
|
+
const name = payload.subarray(offset, offset + nameLen).toString('utf8');
|
|
120
|
+
offset += nameLen;
|
|
121
|
+
|
|
122
|
+
const valueLen = payload.readInt16BE(offset);
|
|
123
|
+
offset += 2;
|
|
124
|
+
const value = payload.subarray(offset, offset + valueLen).toString('utf8');
|
|
125
|
+
offset += valueLen;
|
|
126
|
+
|
|
127
|
+
const hasSignature = payload.readInt8(offset) === 1;
|
|
128
|
+
offset += 1;
|
|
129
|
+
|
|
130
|
+
let signature = null;
|
|
131
|
+
if (hasSignature) {
|
|
132
|
+
const sigLen = payload.readInt16BE(offset);
|
|
133
|
+
offset += 2;
|
|
134
|
+
signature = payload.subarray(offset, offset + sigLen).toString('utf8');
|
|
135
|
+
offset += sigLen;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
properties.push({ name, value, signature });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
success: true,
|
|
144
|
+
version,
|
|
145
|
+
clientIP,
|
|
146
|
+
uuid,
|
|
147
|
+
username,
|
|
148
|
+
properties
|
|
149
|
+
};
|
|
150
|
+
} catch (e) {
|
|
151
|
+
return { success: false, reason: 'PARSE_ERROR', error: e.message };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static create(secret, clientIP, uuid, username, properties = [], version = 1) {
|
|
156
|
+
const ipBuffer = Buffer.from(clientIP, 'utf8');
|
|
157
|
+
const usernameBuffer = Buffer.from(username, 'utf8');
|
|
158
|
+
const uuidBytes = uuid.toBytes();
|
|
159
|
+
|
|
160
|
+
let propsSize = 4;
|
|
161
|
+
const propBuffers = [];
|
|
162
|
+
for (const prop of properties) {
|
|
163
|
+
const nameBuffer = Buffer.from(prop.name, 'utf8');
|
|
164
|
+
const valueBuffer = Buffer.from(prop.value, 'utf8');
|
|
165
|
+
propBuffers.push({ name: nameBuffer, value: valueBuffer, signature: prop.signature });
|
|
166
|
+
propsSize += 2 + nameBuffer.length + 2 + valueBuffer.length + 1;
|
|
167
|
+
if (prop.signature) {
|
|
168
|
+
const sigBuffer = Buffer.from(prop.signature, 'utf8');
|
|
169
|
+
propBuffers[propBuffers.length - 1].sigBuffer = sigBuffer;
|
|
170
|
+
propsSize += 2 + sigBuffer.length;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const payloadSize = 4 + 2 + ipBuffer.length + 16 + 2 + usernameBuffer.length + propsSize;
|
|
175
|
+
const payload = Buffer.alloc(payloadSize);
|
|
176
|
+
let offset = 0;
|
|
177
|
+
|
|
178
|
+
payload.writeInt32BE(version, offset);
|
|
179
|
+
offset += 4;
|
|
180
|
+
|
|
181
|
+
payload.writeInt16BE(ipBuffer.length, offset);
|
|
182
|
+
offset += 2;
|
|
183
|
+
ipBuffer.copy(payload, offset);
|
|
184
|
+
offset += ipBuffer.length;
|
|
185
|
+
|
|
186
|
+
uuidBytes.copy(payload, offset);
|
|
187
|
+
offset += 16;
|
|
188
|
+
|
|
189
|
+
payload.writeInt16BE(usernameBuffer.length, offset);
|
|
190
|
+
offset += 2;
|
|
191
|
+
usernameBuffer.copy(payload, offset);
|
|
192
|
+
offset += usernameBuffer.length;
|
|
193
|
+
|
|
194
|
+
payload.writeInt32BE(properties.length, offset);
|
|
195
|
+
offset += 4;
|
|
196
|
+
|
|
197
|
+
for (const prop of propBuffers) {
|
|
198
|
+
payload.writeInt16BE(prop.name.length, offset);
|
|
199
|
+
offset += 2;
|
|
200
|
+
prop.name.copy(payload, offset);
|
|
201
|
+
offset += prop.name.length;
|
|
202
|
+
|
|
203
|
+
payload.writeInt16BE(prop.value.length, offset);
|
|
204
|
+
offset += 2;
|
|
205
|
+
prop.value.copy(payload, offset);
|
|
206
|
+
offset += prop.value.length;
|
|
207
|
+
|
|
208
|
+
if (prop.signature && prop.sigBuffer) {
|
|
209
|
+
payload.writeInt8(1, offset);
|
|
210
|
+
offset += 1;
|
|
211
|
+
payload.writeInt16BE(prop.sigBuffer.length, offset);
|
|
212
|
+
offset += 2;
|
|
213
|
+
prop.sigBuffer.copy(payload, offset);
|
|
214
|
+
offset += prop.sigBuffer.length;
|
|
215
|
+
} else {
|
|
216
|
+
payload.writeInt8(0, offset);
|
|
217
|
+
offset += 1;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const signature = createHmac('sha256', secret)
|
|
222
|
+
.update(payload)
|
|
223
|
+
.digest();
|
|
224
|
+
|
|
225
|
+
return Buffer.concat([signature, payload]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
12
229
|
export class AuthHandler {
|
|
13
230
|
constructor(options = {}) {
|
|
14
231
|
this.mode = options.mode || AuthMode.OFFLINE;
|
|
15
232
|
this.preventProxyConnections = options.preventProxyConnections || false;
|
|
16
233
|
this.velocitySecret = options.velocitySecret || null;
|
|
234
|
+
this.bungeeCordEnabled = options.bungeeCordEnabled || (this.mode === AuthMode.BUNGEECORD);
|
|
17
235
|
this.keyPair = null;
|
|
18
236
|
}
|
|
19
237
|
|
|
@@ -23,6 +241,34 @@ export class AuthHandler {
|
|
|
23
241
|
}
|
|
24
242
|
}
|
|
25
243
|
|
|
244
|
+
handleBungeeCordForwarding(serverAddress) {
|
|
245
|
+
if (this.mode !== AuthMode.BUNGEECORD) {
|
|
246
|
+
return { success: false, reason: 'BUNGEECORD_NOT_ENABLED' };
|
|
247
|
+
}
|
|
248
|
+
return BungeeCordForwarding.parse(serverAddress);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
handleVelocityForwarding(data) {
|
|
252
|
+
if (this.mode !== AuthMode.VELOCITY) {
|
|
253
|
+
return { success: false, reason: 'VELOCITY_NOT_ENABLED' };
|
|
254
|
+
}
|
|
255
|
+
if (!this.velocitySecret) {
|
|
256
|
+
return { success: false, reason: 'NO_VELOCITY_SECRET' };
|
|
257
|
+
}
|
|
258
|
+
return VelocityForwarding.verify(data, this.velocitySecret);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
createBungeeCordHandshake(hostname, clientIP, uuid, properties = []) {
|
|
262
|
+
return BungeeCordForwarding.create(hostname, clientIP, uuid, properties);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
createVelocityForwarding(clientIP, uuid, username, properties = []) {
|
|
266
|
+
if (!this.velocitySecret) {
|
|
267
|
+
throw new Error('Velocity secret not configured');
|
|
268
|
+
}
|
|
269
|
+
return VelocityForwarding.create(this.velocitySecret, clientIP, uuid, username, properties);
|
|
270
|
+
}
|
|
271
|
+
|
|
26
272
|
getPublicKey() {
|
|
27
273
|
if (!this.keyPair) return null;
|
|
28
274
|
return this.keyPair.publicKey;
|
package/src/index.js
CHANGED
|
@@ -15,4 +15,6 @@ export { Logger, LogLevel } from './utils/Logger.js';
|
|
|
15
15
|
|
|
16
16
|
export { MinecraftEncryption, EncryptedConnection } from './crypto/Encryption.js';
|
|
17
17
|
export { MojangAPI } from './auth/MojangAPI.js';
|
|
18
|
-
export { AuthHandler, ClientAuthHandler, AuthMode } from './auth/AuthHandler.js';
|
|
18
|
+
export { AuthHandler, ClientAuthHandler, AuthMode, BungeeCordForwarding, VelocityForwarding } from './auth/AuthHandler.js';
|
|
19
|
+
|
|
20
|
+
export * from './advanced/index.js';
|