@omindu/yaksha 1.0.0 → 1.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omindu/yaksha",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Lightning-fast, lightweight VPN protocol library with advanced firewall bypass capabilities",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -251,10 +251,10 @@ class YakshaClient extends EventEmitter {
251
251
 
252
252
  const handler = (data) => {
253
253
  try {
254
- const { header, payload } = this.protocol.deserialize(data);
254
+ const packet = this.protocol.deserialize(data);
255
255
 
256
- if (header.type === Protocol.PACKET_TYPES.HANDSHAKE) {
257
- const response = JSON.parse(payload.toString('utf8'));
256
+ if (packet.type === Protocol.PACKET_TYPES.HANDSHAKE) {
257
+ const response = JSON.parse(packet.payload.toString('utf8'));
258
258
 
259
259
  // Store session ID
260
260
  this.sessionId = response.sessionId;
@@ -296,15 +296,15 @@ class YakshaClient extends EventEmitter {
296
296
  this.emit('_rawData', data);
297
297
 
298
298
  try {
299
- const { header, payload } = this.protocol.deserialize(data);
299
+ const packet = this.protocol.deserialize(data);
300
300
 
301
301
  this.stats.packetsReceived++;
302
302
  this.stats.bytesReceived += data.length;
303
303
 
304
304
  // Handle different packet types
305
- switch (header.type) {
305
+ switch (packet.type) {
306
306
  case Protocol.PACKET_TYPES.DATA:
307
- this._handleDataPacket(payload);
307
+ this._handleDataPacket(packet.payload);
308
308
  break;
309
309
 
310
310
  case Protocol.PACKET_TYPES.KEEPALIVE:
@@ -1,12 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Yaksha Protocol Handler
5
- * Custom protocol for packet parsing and handling
4
+ * Yaksha Protocol Implementation (v1.1.0 - Cross-Platform Fixed)
5
+ * Fixed for Linux server <-> Windows client compatibility
6
+ * - Explicit unsigned integer operations
7
+ * - Platform-independent CRC32 checksum
8
+ * - Robust buffer handling with proper byte ordering
6
9
  */
7
10
 
8
11
  const crypto = require('crypto');
9
- const { globalPool } = require('../utils/buffer-pool');
12
+
13
+ // Protocol constants
14
+ const PROTOCOL_VERSION = 0x01;
15
+ const HEADER_SIZE = 16; // bytes
10
16
 
11
17
  // Packet types
12
18
  const PACKET_TYPES = {
@@ -17,12 +23,6 @@ const PACKET_TYPES = {
17
23
  KEEPALIVE: 0x05
18
24
  };
19
25
 
20
- // Protocol version
21
- const PROTOCOL_VERSION = 0x01;
22
-
23
- // Header size (fixed 16 bytes)
24
- const HEADER_SIZE = 16;
25
-
26
26
  class Protocol {
27
27
  constructor(options = {}) {
28
28
  this.version = PROTOCOL_VERSION;
@@ -31,56 +31,59 @@ class Protocol {
31
31
  }
32
32
 
33
33
  /**
34
- * Create a packet header
35
- * Header structure (16 bytes):
36
- * - Version (1 byte)
37
- * - Type (1 byte)
38
- * - Length (2 bytes) - payload length
39
- * - Session ID (4 bytes)
40
- * - Sequence (4 bytes)
41
- * - Checksum (4 bytes)
34
+ * Create protocol header with explicit byte ordering
35
+ * CRITICAL: Uses big-endian for all multi-byte values for cross-platform consistency
42
36
  */
43
- createHeader(type, payloadLength, sessionId, sequence) {
44
- const header = globalPool.acquire(HEADER_SIZE, true);
45
-
46
- // Version
47
- header.writeUInt8(this.version, 0);
48
-
49
- // Type
50
- header.writeUInt8(type, 1);
51
-
52
- // Length (uint16, max 65535)
53
- header.writeUInt16BE(payloadLength, 2);
54
-
55
- // Session ID (uint32)
56
- header.writeUInt32BE(sessionId, 4);
57
-
58
- // Sequence (uint32)
59
- header.writeUInt32BE(sequence, 8);
60
-
61
- // Checksum placeholder (will be filled later)
62
- header.writeUInt32BE(0, 12);
63
-
37
+ createHeader(type, length, sessionId, sequence) {
38
+ const header = Buffer.alloc(HEADER_SIZE); // Zero-filled for consistency
39
+
40
+ // Force unsigned integers to prevent sign issues across platforms
41
+ const safeType = (type & 0xFF) >>> 0;
42
+ const safeLength = (length & 0xFFFF) >>> 0;
43
+ const safeSessionId = (sessionId >>> 0);
44
+ const safeSequence = (sequence >>> 0);
45
+
46
+ header.writeUInt8(this.version, 0); // Version (1 byte)
47
+ header.writeUInt8(safeType, 1); // Type (1 byte)
48
+ header.writeUInt16BE(safeLength, 2); // Length (2 bytes, big-endian)
49
+ header.writeUInt32BE(safeSessionId, 4); // Session ID (4 bytes, big-endian)
50
+ header.writeUInt32BE(safeSequence, 8); // Sequence (4 bytes, big-endian)
51
+ header.writeUInt32BE(0, 12); // Checksum placeholder (4 bytes)
52
+
64
53
  return header;
65
54
  }
66
55
 
67
56
  /**
68
- * Calculate CRC32 checksum
57
+ * Calculate CRC32 checksum with explicit unsigned operations
58
+ * CRITICAL FIX: Ensures same result on Linux and Windows
69
59
  */
70
60
  calculateChecksum(data) {
71
- // Simple CRC32 implementation
72
- let crc = 0xFFFFFFFF;
61
+ // Ensure we're working with a Buffer
62
+ if (!Buffer.isBuffer(data)) {
63
+ data = Buffer.from(data);
64
+ }
65
+
66
+ let crc = 0xFFFFFFFF >>> 0; // Force unsigned
67
+
73
68
  for (let i = 0; i < data.length; i++) {
74
- crc ^= data[i];
69
+ const byte = data[i] & 0xFF; // Ensure byte is 0-255
70
+ crc = (crc ^ byte) >>> 0;
71
+
75
72
  for (let j = 0; j < 8; j++) {
76
- crc = (crc >>> 1) ^ (0xEDB88320 & -(crc & 1));
73
+ // Explicit unsigned right shift and bitwise operations
74
+ if ((crc & 1) !== 0) {
75
+ crc = ((crc >>> 1) ^ 0xEDB88320) >>> 0;
76
+ } else {
77
+ crc = (crc >>> 1) >>> 0;
78
+ }
77
79
  }
78
80
  }
79
- return (crc ^ 0xFFFFFFFF) >>> 0;
81
+
82
+ return ((crc ^ 0xFFFFFFFF) >>> 0);
80
83
  }
81
84
 
82
85
  /**
83
- * Add random padding for traffic obfuscation
86
+ * Add random padding for obfuscation
84
87
  */
85
88
  addPadding(data, maxPaddingSize = 255) {
86
89
  if (!this.enablePadding) {
@@ -102,27 +105,48 @@ class Protocol {
102
105
  * Remove padding from data
103
106
  */
104
107
  removePadding(data, paddingSize) {
105
- if (paddingSize === 0) {
108
+ if (paddingSize === 0 || !paddingSize) {
106
109
  return data;
107
110
  }
108
111
  return data.slice(0, -paddingSize);
109
112
  }
110
113
 
111
114
  /**
112
- * Serialize packet
115
+ * Serialize packet (Linux/Windows compatible)
116
+ * CRITICAL FIX: Proper buffer construction and checksum calculation
113
117
  */
114
118
  serialize(type, payload, sessionId, sequence) {
119
+ // Ensure payload is a proper Buffer
120
+ if (!Buffer.isBuffer(payload)) {
121
+ if (typeof payload === 'string') {
122
+ payload = Buffer.from(payload, 'utf8');
123
+ } else {
124
+ payload = Buffer.from(payload);
125
+ }
126
+ }
127
+
115
128
  // Add padding to payload
116
129
  const { data: paddedPayload, paddingSize } = this.addPadding(payload);
117
130
 
118
- // Create header
119
- const header = this.createHeader(type, paddedPayload.length, sessionId, sequence);
131
+ // Force safe unsigned integers
132
+ const safeSessionId = (sessionId >>> 0);
133
+ const safeSequence = (sequence >>> 0);
134
+
135
+ // Create header with zero checksum
136
+ const header = this.createHeader(type, paddedPayload.length, safeSessionId, safeSequence);
120
137
 
121
- // Combine header and payload
122
- const packet = Buffer.concat([header, paddedPayload]);
138
+ // Create complete packet buffer (avoid Buffer.concat for consistency)
139
+ const totalSize = header.length + paddedPayload.length;
140
+ const packet = Buffer.allocUnsafe(totalSize);
141
+
142
+ // Copy header and payload explicitly
143
+ header.copy(packet, 0, 0, header.length);
144
+ paddedPayload.copy(packet, header.length, 0, paddedPayload.length);
123
145
 
124
- // Calculate and update checksum (checksum covers header + payload)
125
- const checksum = this.calculateChecksum(packet);
146
+ // Calculate checksum over entire packet (with checksum field zeroed)
147
+ const checksum = this.calculateChecksum(packet) >>> 0;
148
+
149
+ // Write checksum to header
126
150
  packet.writeUInt32BE(checksum, 12);
127
151
 
128
152
  return { packet, paddingSize };
@@ -132,20 +156,25 @@ class Protocol {
132
156
  * Parse packet header
133
157
  */
134
158
  parseHeader(buffer) {
159
+ if (!Buffer.isBuffer(buffer)) {
160
+ throw new Error('Invalid buffer: not a Buffer object');
161
+ }
162
+
135
163
  if (buffer.length < HEADER_SIZE) {
136
- throw new Error('Buffer too small for header');
164
+ throw new Error(`Buffer too small for header: ${buffer.length} bytes (need ${HEADER_SIZE})`);
137
165
  }
138
166
 
139
- const version = buffer.readUInt8(0);
140
- const type = buffer.readUInt8(1);
141
- const length = buffer.readUInt16BE(2);
142
- const sessionId = buffer.readUInt32BE(4);
143
- const sequence = buffer.readUInt32BE(8);
144
- const checksum = buffer.readUInt32BE(12);
167
+ // Read with explicit unsigned operations
168
+ const version = buffer.readUInt8(0) & 0xFF;
169
+ const type = buffer.readUInt8(1) & 0xFF;
170
+ const length = buffer.readUInt16BE(2) >>> 0;
171
+ const sessionId = buffer.readUInt32BE(4) >>> 0;
172
+ const sequence = buffer.readUInt32BE(8) >>> 0;
173
+ const checksum = buffer.readUInt32BE(12) >>> 0;
145
174
 
146
175
  // Validate version
147
176
  if (version !== this.version) {
148
- throw new Error(`Unsupported protocol version: ${version}`);
177
+ throw new Error(`Unsupported protocol version: ${version} (expected ${this.version})`);
149
178
  }
150
179
 
151
180
  // Validate type
@@ -155,7 +184,7 @@ class Protocol {
155
184
 
156
185
  // Validate length
157
186
  if (length > this.maxPayloadSize) {
158
- throw new Error(`Payload too large: ${length}`);
187
+ throw new Error(`Payload too large: ${length} (max ${this.maxPayloadSize})`);
159
188
  }
160
189
 
161
190
  return {
@@ -169,99 +198,143 @@ class Protocol {
169
198
  }
170
199
 
171
200
  /**
172
- * Deserialize packet
201
+ * Deserialize packet (Linux/Windows compatible)
202
+ * CRITICAL FIX: Robust validation and error reporting
173
203
  */
174
204
  deserialize(buffer, paddingSize = 0) {
175
- // Parse header
176
- const header = this.parseHeader(buffer);
205
+ if (!Buffer.isBuffer(buffer)) {
206
+ throw new Error('Invalid buffer: not a Buffer object');
207
+ }
177
208
 
178
- // Check if we have complete packet
179
- const expectedSize = HEADER_SIZE + header.length;
180
- if (buffer.length < expectedSize) {
181
- throw new Error(`Incomplete packet: expected ${expectedSize}, got ${buffer.length}`);
209
+ // Parse header first
210
+ const header = this.parseHeader(buffer);
211
+
212
+ const totalExpectedSize = HEADER_SIZE + header.length;
213
+ if (buffer.length < totalExpectedSize) {
214
+ throw new Error(
215
+ `Incomplete packet: got ${buffer.length} bytes, expected ${totalExpectedSize} ` +
216
+ `(header=${HEADER_SIZE}, payload=${header.length})`
217
+ );
182
218
  }
183
219
 
184
- // Verify checksum
185
- const storedChecksum = header.checksum;
220
+ // Extract payload (create new buffer to avoid reference issues)
221
+ const payloadStart = HEADER_SIZE;
222
+ const payloadEnd = payloadStart + header.length;
223
+ const payload = Buffer.allocUnsafe(header.length);
224
+ buffer.copy(payload, 0, payloadStart, payloadEnd);
225
+
226
+ // Verify checksum - recalculate with checksum field zeroed
227
+ const verifyBuffer = Buffer.allocUnsafe(buffer.length);
228
+ buffer.copy(verifyBuffer, 0, 0, buffer.length);
229
+ verifyBuffer.writeUInt32BE(0, 12); // Zero out checksum field
186
230
 
187
- // Create a copy of packet and zero out checksum field for verification
188
- const packetCopy = Buffer.from(buffer.slice(0, expectedSize));
189
- packetCopy.writeUInt32BE(0, 12);
231
+ const calculatedChecksum = this.calculateChecksum(verifyBuffer) >>> 0;
232
+ const receivedChecksum = header.checksum >>> 0;
190
233
 
191
- const calculatedChecksum = this.calculateChecksum(packetCopy);
192
-
193
- if (storedChecksum !== calculatedChecksum) {
194
- throw new Error('Checksum verification failed');
234
+ if (receivedChecksum !== calculatedChecksum) {
235
+ // Detailed error for debugging cross-platform issues
236
+ const debugInfo = {
237
+ received: '0x' + receivedChecksum.toString(16).padStart(8, '0').toUpperCase(),
238
+ calculated: '0x' + calculatedChecksum.toString(16).padStart(8, '0').toUpperCase(),
239
+ packetSize: buffer.length,
240
+ headerSize: HEADER_SIZE,
241
+ payloadSize: header.length,
242
+ type: header.type,
243
+ sequence: header.sequence,
244
+ platform: process.platform,
245
+ nodeVersion: process.version
246
+ };
247
+
248
+ throw new Error(
249
+ `Checksum verification failed!\n` +
250
+ ` Received: ${debugInfo.received}\n` +
251
+ ` Calculated: ${debugInfo.calculated}\n` +
252
+ ` Packet: ${debugInfo.packetSize} bytes (header=${debugInfo.headerSize}, payload=${debugInfo.payloadSize})\n` +
253
+ ` Type: ${debugInfo.type}, Sequence: ${debugInfo.sequence}\n` +
254
+ ` Platform: ${debugInfo.platform}, Node: ${debugInfo.nodeVersion}`
255
+ );
195
256
  }
196
257
 
197
- // Extract payload
198
- let payload = buffer.slice(HEADER_SIZE, expectedSize);
199
-
200
- // Remove padding if present
201
- payload = this.removePadding(payload, paddingSize);
258
+ // Remove padding from payload
259
+ const unpaddedPayload = this.removePadding(payload, paddingSize);
202
260
 
203
261
  return {
204
- header,
205
- payload,
206
- totalSize: expectedSize
262
+ type: header.type,
263
+ payload: unpaddedPayload,
264
+ sessionId: header.sessionId,
265
+ sequence: header.sequence,
266
+ checksum: header.checksum
207
267
  };
208
268
  }
209
269
 
210
270
  /**
211
- * Create HANDSHAKE packet
271
+ * Get packet type name
212
272
  */
213
- createHandshake(sessionId, sequence, data) {
214
- return this.serialize(PACKET_TYPES.HANDSHAKE, data, sessionId, sequence);
273
+ getPacketTypeName(type) {
274
+ for (const [name, value] of Object.entries(PACKET_TYPES)) {
275
+ if (value === type) {
276
+ return name;
277
+ }
278
+ }
279
+ return `UNKNOWN(${type})`;
215
280
  }
216
281
 
217
282
  /**
218
- * Create DATA packet
283
+ * Create handshake packet
284
+ * @param {number} sessionId - Session identifier
285
+ * @param {number} sequence - Packet sequence number
286
+ * @param {Buffer} payload - Handshake data (usually JSON)
287
+ * @returns {Object} { packet: Buffer, paddingSize: number }
219
288
  */
220
- createData(sessionId, sequence, data) {
221
- return this.serialize(PACKET_TYPES.DATA, data, sessionId, sequence);
289
+ createHandshake(sessionId, sequence, payload) {
290
+ return this.serialize(PACKET_TYPES.HANDSHAKE, payload, sessionId, sequence);
222
291
  }
223
292
 
224
293
  /**
225
- * Create ACK packet
294
+ * Create data packet
295
+ * @param {number} sessionId - Session identifier
296
+ * @param {number} sequence - Packet sequence number
297
+ * @param {Buffer} payload - Data payload
298
+ * @returns {Object} { packet: Buffer, paddingSize: number }
226
299
  */
227
- createAck(sessionId, sequence, ackSequence) {
228
- const ackBuffer = Buffer.allocUnsafe(4);
229
- ackBuffer.writeUInt32BE(ackSequence, 0);
230
- return this.serialize(PACKET_TYPES.ACK, ackBuffer, sessionId, sequence);
300
+ createData(sessionId, sequence, payload) {
301
+ return this.serialize(PACKET_TYPES.DATA, payload, sessionId, sequence);
231
302
  }
232
303
 
233
304
  /**
234
- * Create CLOSE packet
305
+ * Create ACK packet
306
+ * @param {number} sessionId - Session identifier
307
+ * @param {number} sequence - Packet sequence number
308
+ * @param {Buffer} payload - ACK data (optional)
309
+ * @returns {Object} { packet: Buffer, paddingSize: number }
235
310
  */
236
- createClose(sessionId, sequence, reason = '') {
237
- const reasonBuffer = Buffer.from(reason, 'utf8');
238
- return this.serialize(PACKET_TYPES.CLOSE, reasonBuffer, sessionId, sequence);
311
+ createAck(sessionId, sequence, payload = Buffer.alloc(0)) {
312
+ return this.serialize(PACKET_TYPES.ACK, payload, sessionId, sequence);
239
313
  }
240
314
 
241
315
  /**
242
- * Create KEEPALIVE packet
316
+ * Create close packet
317
+ * @param {number} sessionId - Session identifier
318
+ * @param {number} sequence - Packet sequence number
319
+ * @param {Buffer} payload - Close reason (optional)
320
+ * @returns {Object} { packet: Buffer, paddingSize: number }
243
321
  */
244
- createKeepalive(sessionId, sequence) {
245
- const timestamp = Buffer.allocUnsafe(8);
246
- timestamp.writeBigUInt64BE(BigInt(Date.now()), 0);
247
- return this.serialize(PACKET_TYPES.KEEPALIVE, timestamp, sessionId, sequence);
322
+ createClose(sessionId, sequence, payload = Buffer.alloc(0)) {
323
+ return this.serialize(PACKET_TYPES.CLOSE, payload, sessionId, sequence);
248
324
  }
249
325
 
250
326
  /**
251
- * Get packet type name
327
+ * Create keepalive packet
328
+ * @param {number} sessionId - Session identifier
329
+ * @param {number} sequence - Packet sequence number
330
+ * @returns {Object} { packet: Buffer, paddingSize: number }
252
331
  */
253
- getTypeName(type) {
254
- const typeNames = {
255
- [PACKET_TYPES.HANDSHAKE]: 'HANDSHAKE',
256
- [PACKET_TYPES.DATA]: 'DATA',
257
- [PACKET_TYPES.ACK]: 'ACK',
258
- [PACKET_TYPES.CLOSE]: 'CLOSE',
259
- [PACKET_TYPES.KEEPALIVE]: 'KEEPALIVE'
260
- };
261
- return typeNames[type] || 'UNKNOWN';
332
+ createKeepalive(sessionId, sequence) {
333
+ return this.serialize(PACKET_TYPES.KEEPALIVE, Buffer.alloc(0), sessionId, sequence);
262
334
  }
263
335
  }
264
336
 
337
+ // Export
265
338
  module.exports = Protocol;
266
339
  module.exports.PACKET_TYPES = PACKET_TYPES;
267
340
  module.exports.HEADER_SIZE = HEADER_SIZE;
@@ -0,0 +1,268 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Yaksha Protocol Handler
5
+ * Custom protocol for packet parsing and handling
6
+ */
7
+
8
+ const crypto = require('crypto');
9
+ const { globalPool } = require('../utils/buffer-pool');
10
+
11
+ // Packet types
12
+ const PACKET_TYPES = {
13
+ HANDSHAKE: 0x01,
14
+ DATA: 0x02,
15
+ ACK: 0x03,
16
+ CLOSE: 0x04,
17
+ KEEPALIVE: 0x05
18
+ };
19
+
20
+ // Protocol version
21
+ const PROTOCOL_VERSION = 0x01;
22
+
23
+ // Header size (fixed 16 bytes)
24
+ const HEADER_SIZE = 16;
25
+
26
+ class Protocol {
27
+ constructor(options = {}) {
28
+ this.version = PROTOCOL_VERSION;
29
+ this.maxPayloadSize = options.maxPayloadSize || 65535;
30
+ this.enablePadding = options.enablePadding !== false;
31
+ }
32
+
33
+ /**
34
+ * Create a packet header
35
+ * Header structure (16 bytes):
36
+ * - Version (1 byte)
37
+ * - Type (1 byte)
38
+ * - Length (2 bytes) - payload length
39
+ * - Session ID (4 bytes)
40
+ * - Sequence (4 bytes)
41
+ * - Checksum (4 bytes)
42
+ */
43
+ createHeader(type, payloadLength, sessionId, sequence) {
44
+ const header = globalPool.acquire(HEADER_SIZE, true);
45
+
46
+ // Version
47
+ header.writeUInt8(this.version, 0);
48
+
49
+ // Type
50
+ header.writeUInt8(type, 1);
51
+
52
+ // Length (uint16, max 65535)
53
+ header.writeUInt16BE(payloadLength, 2);
54
+
55
+ // Session ID (uint32)
56
+ header.writeUInt32BE(sessionId, 4);
57
+
58
+ // Sequence (uint32)
59
+ header.writeUInt32BE(sequence, 8);
60
+
61
+ // Checksum placeholder (will be filled later)
62
+ header.writeUInt32BE(0, 12);
63
+
64
+ return header;
65
+ }
66
+
67
+ /**
68
+ * Calculate CRC32 checksum
69
+ */
70
+ calculateChecksum(data) {
71
+ // Simple CRC32 implementation
72
+ let crc = 0xFFFFFFFF;
73
+ for (let i = 0; i < data.length; i++) {
74
+ crc ^= data[i];
75
+ for (let j = 0; j < 8; j++) {
76
+ crc = (crc >>> 1) ^ (0xEDB88320 & -(crc & 1));
77
+ }
78
+ }
79
+ return (crc ^ 0xFFFFFFFF) >>> 0;
80
+ }
81
+
82
+ /**
83
+ * Add random padding for traffic obfuscation
84
+ */
85
+ addPadding(data, maxPaddingSize = 255) {
86
+ if (!this.enablePadding) {
87
+ return { data, paddingSize: 0 };
88
+ }
89
+
90
+ const paddingSize = crypto.randomInt(0, Math.min(maxPaddingSize + 1, 256));
91
+ if (paddingSize === 0) {
92
+ return { data, paddingSize: 0 };
93
+ }
94
+
95
+ const padding = crypto.randomBytes(paddingSize);
96
+ const paddedData = Buffer.concat([data, padding]);
97
+
98
+ return { data: paddedData, paddingSize };
99
+ }
100
+
101
+ /**
102
+ * Remove padding from data
103
+ */
104
+ removePadding(data, paddingSize) {
105
+ if (paddingSize === 0) {
106
+ return data;
107
+ }
108
+ return data.slice(0, -paddingSize);
109
+ }
110
+
111
+ /**
112
+ * Serialize packet
113
+ */
114
+ serialize(type, payload, sessionId, sequence) {
115
+ // Add padding to payload
116
+ const { data: paddedPayload, paddingSize } = this.addPadding(payload);
117
+
118
+ // Create header
119
+ const header = this.createHeader(type, paddedPayload.length, sessionId, sequence);
120
+
121
+ // Combine header and payload
122
+ const packet = Buffer.concat([header, paddedPayload]);
123
+
124
+ // Calculate and update checksum (checksum covers header + payload)
125
+ const checksum = this.calculateChecksum(packet);
126
+ packet.writeUInt32BE(checksum, 12);
127
+
128
+ return { packet, paddingSize };
129
+ }
130
+
131
+ /**
132
+ * Parse packet header
133
+ */
134
+ parseHeader(buffer) {
135
+ if (buffer.length < HEADER_SIZE) {
136
+ throw new Error('Buffer too small for header');
137
+ }
138
+
139
+ const version = buffer.readUInt8(0);
140
+ const type = buffer.readUInt8(1);
141
+ const length = buffer.readUInt16BE(2);
142
+ const sessionId = buffer.readUInt32BE(4);
143
+ const sequence = buffer.readUInt32BE(8);
144
+ const checksum = buffer.readUInt32BE(12);
145
+
146
+ // Validate version
147
+ if (version !== this.version) {
148
+ throw new Error(`Unsupported protocol version: ${version}`);
149
+ }
150
+
151
+ // Validate type
152
+ if (!Object.values(PACKET_TYPES).includes(type)) {
153
+ throw new Error(`Invalid packet type: ${type}`);
154
+ }
155
+
156
+ // Validate length
157
+ if (length > this.maxPayloadSize) {
158
+ throw new Error(`Payload too large: ${length}`);
159
+ }
160
+
161
+ return {
162
+ version,
163
+ type,
164
+ length,
165
+ sessionId,
166
+ sequence,
167
+ checksum
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Deserialize packet
173
+ */
174
+ deserialize(buffer, paddingSize = 0) {
175
+ // Parse header
176
+ const header = this.parseHeader(buffer);
177
+
178
+ // Check if we have complete packet
179
+ const expectedSize = HEADER_SIZE + header.length;
180
+ if (buffer.length < expectedSize) {
181
+ throw new Error(`Incomplete packet: expected ${expectedSize}, got ${buffer.length}`);
182
+ }
183
+
184
+ // Verify checksum
185
+ const storedChecksum = header.checksum;
186
+
187
+ // Create a copy of packet and zero out checksum field for verification
188
+ const packetCopy = Buffer.from(buffer.slice(0, expectedSize));
189
+ packetCopy.writeUInt32BE(0, 12);
190
+
191
+ const calculatedChecksum = this.calculateChecksum(packetCopy);
192
+
193
+ if (storedChecksum !== calculatedChecksum) {
194
+ throw new Error('Checksum verification failed');
195
+ }
196
+
197
+ // Extract payload
198
+ let payload = buffer.slice(HEADER_SIZE, expectedSize);
199
+
200
+ // Remove padding if present
201
+ payload = this.removePadding(payload, paddingSize);
202
+
203
+ return {
204
+ header,
205
+ payload,
206
+ totalSize: expectedSize
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Create HANDSHAKE packet
212
+ */
213
+ createHandshake(sessionId, sequence, data) {
214
+ return this.serialize(PACKET_TYPES.HANDSHAKE, data, sessionId, sequence);
215
+ }
216
+
217
+ /**
218
+ * Create DATA packet
219
+ */
220
+ createData(sessionId, sequence, data) {
221
+ return this.serialize(PACKET_TYPES.DATA, data, sessionId, sequence);
222
+ }
223
+
224
+ /**
225
+ * Create ACK packet
226
+ */
227
+ createAck(sessionId, sequence, ackSequence) {
228
+ const ackBuffer = Buffer.allocUnsafe(4);
229
+ ackBuffer.writeUInt32BE(ackSequence, 0);
230
+ return this.serialize(PACKET_TYPES.ACK, ackBuffer, sessionId, sequence);
231
+ }
232
+
233
+ /**
234
+ * Create CLOSE packet
235
+ */
236
+ createClose(sessionId, sequence, reason = '') {
237
+ const reasonBuffer = Buffer.from(reason, 'utf8');
238
+ return this.serialize(PACKET_TYPES.CLOSE, reasonBuffer, sessionId, sequence);
239
+ }
240
+
241
+ /**
242
+ * Create KEEPALIVE packet
243
+ */
244
+ createKeepalive(sessionId, sequence) {
245
+ const timestamp = Buffer.allocUnsafe(8);
246
+ timestamp.writeBigUInt64BE(BigInt(Date.now()), 0);
247
+ return this.serialize(PACKET_TYPES.KEEPALIVE, timestamp, sessionId, sequence);
248
+ }
249
+
250
+ /**
251
+ * Get packet type name
252
+ */
253
+ getTypeName(type) {
254
+ const typeNames = {
255
+ [PACKET_TYPES.HANDSHAKE]: 'HANDSHAKE',
256
+ [PACKET_TYPES.DATA]: 'DATA',
257
+ [PACKET_TYPES.ACK]: 'ACK',
258
+ [PACKET_TYPES.CLOSE]: 'CLOSE',
259
+ [PACKET_TYPES.KEEPALIVE]: 'KEEPALIVE'
260
+ };
261
+ return typeNames[type] || 'UNKNOWN';
262
+ }
263
+ }
264
+
265
+ module.exports = Protocol;
266
+ module.exports.PACKET_TYPES = PACKET_TYPES;
267
+ module.exports.HEADER_SIZE = HEADER_SIZE;
268
+ module.exports.PROTOCOL_VERSION = PROTOCOL_VERSION;
@@ -242,20 +242,20 @@ class YakshaServer extends EventEmitter {
242
242
  _handleTCPData(buffer, client) {
243
243
  try {
244
244
  // Parse packet
245
- const { header, payload, totalSize } = this.protocol.deserialize(buffer);
245
+ const packet = this.protocol.deserialize(buffer);
246
246
 
247
247
  this.stats.packetsReceived++;
248
- this.stats.bytesReceived += totalSize;
248
+ this.stats.bytesReceived += buffer.length;
249
249
  client.lastActivity = Date.now();
250
250
 
251
251
  // Handle different packet types
252
- switch (header.type) {
252
+ switch (packet.type) {
253
253
  case Protocol.PACKET_TYPES.HANDSHAKE:
254
- this._handleHandshake(payload, client);
254
+ this._handleHandshake(packet.payload, client);
255
255
  break;
256
256
 
257
257
  case Protocol.PACKET_TYPES.DATA:
258
- this._handleData(payload, client);
258
+ this._handleData(packet.payload, client);
259
259
  break;
260
260
 
261
261
  case Protocol.PACKET_TYPES.KEEPALIVE:
@@ -292,14 +292,14 @@ class YakshaServer extends EventEmitter {
292
292
  }
293
293
 
294
294
  // Process packet
295
- const { payload } = this.protocol.deserialize(data);
295
+ const packet = this.protocol.deserialize(data);
296
296
 
297
297
  this.stats.packetsReceived++;
298
298
  this.stats.bytesReceived += data.length;
299
299
  client.lastActivity = Date.now();
300
300
 
301
301
  // Handle data packet
302
- this._handleData(payload, client, 'udp');
302
+ this._handleData(packet.payload, client, 'udp');
303
303
 
304
304
  } catch (error) {
305
305
  this.logger.error('Error processing UDP data:', error.message);