@cliz/inlets
Version:
Cloud Native Tunnel
456 lines (455 loc) • 19.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProtocolAdapterFactory = exports.NewProtocolAdapter = exports.LegacyProtocolAdapter = exports.parseBinaryMessage = exports.buildBinaryMessage = exports.MessageFlags = exports.MessageType = void 0;
const type_1 = require("./type");
const utils_1 = require("../../utils");
const compression_1 = require("../../utils/compression");
const stream_manager_1 = require("./stream-manager");
const flow_controller_1 = require("./flow-controller");
var MessageType;
(function (MessageType) {
MessageType[MessageType["CONTROL"] = 0] = "CONTROL";
MessageType[MessageType["HTTP_REQUEST"] = 1] = "HTTP_REQUEST";
MessageType[MessageType["HTTP_RESPONSE"] = 2] = "HTTP_RESPONSE";
MessageType[MessageType["TCP_DATA"] = 3] = "TCP_DATA";
MessageType[MessageType["TCP_OPEN"] = 4] = "TCP_OPEN";
MessageType[MessageType["TCP_CLOSE"] = 5] = "TCP_CLOSE";
MessageType[MessageType["FLOW_CONTROL"] = 6] = "FLOW_CONTROL";
})(MessageType = exports.MessageType || (exports.MessageType = {}));
var MessageFlags;
(function (MessageFlags) {
MessageFlags[MessageFlags["FIN"] = 1] = "FIN";
MessageFlags[MessageFlags["ACK"] = 2] = "ACK";
MessageFlags[MessageFlags["BACKPRESSURE"] = 4] = "BACKPRESSURE";
})(MessageFlags = exports.MessageFlags || (exports.MessageFlags = {}));
function buildBinaryMessage(msg) {
const streamIdBuffer = Buffer.from(msg.streamId, 'utf8');
const streamIdLength = streamIdBuffer.length;
if (streamIdLength > 255) {
throw new Error(`Stream ID too long: ${streamIdLength} bytes (max 255)`);
}
const headerSize = 1 + 1 + streamIdLength + 4 + 1 + 4;
const totalSize = headerSize + msg.data.length;
const buffer = Buffer.allocUnsafe(totalSize);
let offset = 0;
buffer.writeUInt8(msg.type, offset);
offset += 1;
buffer.writeUInt8(streamIdLength, offset);
offset += 1;
for (let i = 0; i < streamIdLength; i++) {
buffer[offset + i] = streamIdBuffer[i];
}
offset += streamIdLength;
buffer.writeUInt32BE(msg.sequence, offset);
offset += 4;
buffer.writeUInt8(msg.flags, offset);
offset += 1;
buffer.writeUInt32BE(msg.data.length, offset);
offset += 4;
for (let i = 0; i < msg.data.length; i++) {
buffer[offset + i] = msg.data[i];
}
return buffer;
}
exports.buildBinaryMessage = buildBinaryMessage;
function parseBinaryMessage(buffer) {
if (buffer.length < 11) {
throw new Error(`Message too short: ${buffer.length} bytes (min 11)`);
}
let offset = 0;
const type = buffer.readUInt8(offset);
offset += 1;
const streamIdLength = buffer.readUInt8(offset);
offset += 1;
if (buffer.length < offset + streamIdLength) {
throw new Error(`Message too short for stream ID: ${buffer.length} bytes`);
}
const streamId = buffer.slice(offset, offset + streamIdLength).toString('utf8');
offset += streamIdLength;
const sequence = buffer.readUInt32BE(offset);
offset += 4;
const flags = buffer.readUInt8(offset);
offset += 1;
const dataLength = buffer.readUInt32BE(offset);
offset += 4;
if (buffer.length < offset + dataLength) {
throw new Error(`Message too short for data: ${buffer.length} bytes, expected ${offset + dataLength}`);
}
const data = buffer.slice(offset, offset + dataLength);
return {
type,
streamId,
sequence,
flags,
data,
};
}
exports.parseBinaryMessage = parseBinaryMessage;
class LegacyProtocolAdapter {
constructor(socket, isClient = false) {
this.socket = socket;
this.isClient = isClient;
this.tcpDataHandlers = new Set();
this.setupEventListeners();
}
setupEventListeners() {
if (this.isClient) {
this.socket.on('request', async (data) => {
if (this.httpRequestHandler) {
const decoded = await utils_1.dataProcessor.client.onRequest(data.data);
await this.httpRequestHandler(data.id, Buffer.from(decoded));
}
});
}
else {
this.socket.on('response', async (response) => {
if (this.httpResponseHandler) {
const decoded = await utils_1.dataProcessor.server.onResponse(response.data);
await this.httpResponseHandler(response.id, Buffer.from(decoded, 'base64'));
}
});
}
}
async sendHTTPRequest(id, data) {
const base64Data = data.toString('base64');
const processed = await utils_1.dataProcessor.server.request(base64Data);
this.socket.emit('request', {
id,
data: processed,
});
}
async sendHTTPResponse(id, data) {
const base64Data = data.toString('base64');
const processed = await utils_1.dataProcessor.client.response(base64Data);
this.socket.emit('response', {
id,
data: processed,
});
}
async sendTCPData(streamId, data) {
throw new Error('TCP data transmission not supported in legacy protocol');
}
onHTTPRequest(handler) {
this.httpRequestHandler = handler;
}
onHTTPResponse(handler) {
this.httpResponseHandler = handler;
}
onTCPData(handler) {
this.tcpDataHandlers.add(handler);
return () => this.tcpDataHandlers.delete(handler);
}
}
exports.LegacyProtocolAdapter = LegacyProtocolAdapter;
class NewProtocolAdapter {
constructor(socket, capabilities, isClient = false) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
this.socket = socket;
this.capabilities = capabilities;
this.isClient = isClient;
this.tcpDataHandlers = new Set();
this.sequenceCounter = new Map();
this.compressionAlgorithm = 'brotli';
this.chunkSize = 64 * 1024;
if ((_b = (_a = capabilities.features) === null || _a === void 0 ? void 0 : _a.compression) === null || _b === void 0 ? void 0 : _b.preferred) {
this.compressionAlgorithm = capabilities.features.compression.preferred;
}
else if ((_e = (_d = (_c = capabilities.features) === null || _c === void 0 ? void 0 : _c.compression) === null || _d === void 0 ? void 0 : _d.algorithms) === null || _e === void 0 ? void 0 : _e.length) {
this.compressionAlgorithm = capabilities.features.compression.algorithms[0];
}
if ((_g = (_f = capabilities.features) === null || _f === void 0 ? void 0 : _f.chunkSize) === null || _g === void 0 ? void 0 : _g.default) {
this.chunkSize = capabilities.features.chunkSize.default;
}
if (capabilities.flags & type_1.CapabilityFlags.STREAMING) {
this.streamManager = new stream_manager_1.StreamManager(this.chunkSize);
}
if (capabilities.flags & type_1.CapabilityFlags.FLOW_CONTROL) {
const windowSize = ((_j = (_h = capabilities.features) === null || _h === void 0 ? void 0 : _h.flowControl) === null || _j === void 0 ? void 0 : _j.windowSize) || 1024 * 1024;
this.flowController = new flow_controller_1.FlowController(windowSize, (streamId, pause) => {
this.handleBackpressure(streamId, pause);
});
}
this.setupEventListeners();
}
getNextSequence(streamId) {
const current = this.sequenceCounter.get(streamId) || 0;
const next = current + 1;
this.sequenceCounter.set(streamId, next);
return next;
}
handleBackpressure(streamId, pause) {
if (this.streamManager) {
const stream = this.streamManager.getStream(streamId);
if (stream) {
if (pause) {
stream.pause();
}
else {
stream.resume();
}
}
}
}
async handleBinaryMessage(type, streamId, data, flags, sequence) {
const isLast = !!(flags & MessageFlags.FIN);
const useStreaming = this.capabilities.flags & type_1.CapabilityFlags.STREAMING;
if (this.flowController) {
if (!this.flowController.receive(streamId, data.length)) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
const shouldStream = this.shouldUseStreaming(type);
if (shouldStream && this.streamManager) {
this.streamManager.addChunk(streamId, sequence, data, isLast);
if (isLast) {
return;
}
}
else {
let payload = data;
if (this.shouldUseCompression(type)) {
payload = await (0, compression_1.decompressBuffer)(payload, this.compressionAlgorithm);
}
if (type === MessageType.HTTP_REQUEST && this.isClient && this.httpRequestHandler) {
await this.httpRequestHandler(streamId, payload);
}
else if (type === MessageType.HTTP_RESPONSE && !this.isClient && this.httpResponseHandler) {
await this.httpResponseHandler(streamId, payload);
}
else if (type === MessageType.TCP_DATA && this.tcpDataHandlers.size > 0) {
for (const handler of this.tcpDataHandlers) {
await handler(streamId, payload);
}
}
if (this.flowController) {
this.flowController.releaseReceiveWindow(streamId, data.length);
}
}
}
setupEventListeners() {
if (this.isClient) {
this.socket.on('request', async (data) => {
if (this.httpRequestHandler) {
try {
const messageBuffer = Buffer.from(data.data, 'base64');
const binaryMsg = parseBinaryMessage(messageBuffer);
if (binaryMsg.type === MessageType.HTTP_REQUEST) {
if (this.shouldUseStreaming(binaryMsg.type) && this.streamManager) {
const stream = this.streamManager.getStream(binaryMsg.streamId);
if (!stream) {
this.streamManager.createStream(binaryMsg.streamId, async (completeData) => {
let payload = completeData;
if (this.shouldUseCompression(binaryMsg.type)) {
payload = await (0, compression_1.decompressBuffer)(completeData, this.compressionAlgorithm);
}
if (this.httpRequestHandler) {
await this.httpRequestHandler(binaryMsg.streamId, payload);
}
if (this.flowController) {
this.flowController.releaseReceiveWindow(binaryMsg.streamId, completeData.length);
}
}, (error) => {
console.error('[NewProtocolAdapter] Stream error:', error);
});
}
}
await this.handleBinaryMessage(binaryMsg.type, binaryMsg.streamId, binaryMsg.data, binaryMsg.flags, binaryMsg.sequence);
}
}
catch (error) {
console.error('[NewProtocolAdapter] Failed to parse binary message:', error);
}
}
});
}
else {
this.socket.on('response', async (response) => {
if (this.httpResponseHandler) {
try {
const messageBuffer = Buffer.from(response.data, 'base64');
const binaryMsg = parseBinaryMessage(messageBuffer);
if (binaryMsg.type === MessageType.HTTP_RESPONSE) {
if (this.shouldUseStreaming(binaryMsg.type) && this.streamManager) {
const stream = this.streamManager.getStream(binaryMsg.streamId);
if (!stream) {
this.streamManager.createStream(binaryMsg.streamId, async (completeData) => {
let payload = completeData;
if (this.shouldUseCompression(binaryMsg.type)) {
payload = await (0, compression_1.decompressBuffer)(completeData, this.compressionAlgorithm);
}
if (this.httpResponseHandler) {
await this.httpResponseHandler(binaryMsg.streamId, payload);
}
if (this.flowController) {
this.flowController.releaseReceiveWindow(binaryMsg.streamId, completeData.length);
}
}, (error) => {
console.error('[NewProtocolAdapter] Stream error:', error);
});
}
}
await this.handleBinaryMessage(binaryMsg.type, binaryMsg.streamId, binaryMsg.data, binaryMsg.flags, binaryMsg.sequence);
}
}
catch (error) {
console.error('[NewProtocolAdapter] Failed to parse binary message:', error);
}
}
});
}
this.socket.on('tcp:data', async (data) => {
if (this.tcpDataHandlers.size > 0) {
try {
const messageBuffer = Buffer.from(data.data, 'base64');
const binaryMsg = parseBinaryMessage(messageBuffer);
if (binaryMsg.type === MessageType.TCP_DATA) {
await this.handleBinaryMessage(binaryMsg.type, binaryMsg.streamId, binaryMsg.data, binaryMsg.flags, binaryMsg.sequence);
}
}
catch (error) {
console.error('[NewProtocolAdapter] Failed to parse TCP binary message:', error);
}
}
});
}
shouldUseStreaming(type) {
if (!this.streamManager) {
return false;
}
if (!(this.capabilities.flags & type_1.CapabilityFlags.STREAMING)) {
return false;
}
if (type === MessageType.TCP_DATA) {
return false;
}
return true;
}
shouldUseCompression(type) {
if (!(this.capabilities.flags & type_1.CapabilityFlags.COMPRESSION)) {
return false;
}
if (type === MessageType.TCP_DATA) {
return false;
}
return true;
}
async buildAndSendMessage(type, streamId, data, flags = 0) {
const canStream = this.shouldUseStreaming(type);
const shouldChunk = canStream && data.length > this.chunkSize;
if (shouldChunk && this.streamManager) {
await this.sendStreaming(type, streamId, data);
}
else {
await this.sendDirect(type, streamId, data, flags);
}
}
async sendDirect(type, streamId, data, flags) {
if (this.flowController) {
this.flowController.initializeStream(streamId);
}
let processedData = data;
if (this.shouldUseCompression(type)) {
processedData = await (0, compression_1.compressBuffer)(processedData, this.compressionAlgorithm);
}
if (this.flowController) {
while (!this.flowController.canSend(streamId, processedData.length)) {
await new Promise(resolve => setTimeout(resolve, 50));
}
this.flowController.send(streamId, processedData.length);
}
const sequence = this.getNextSequence(streamId);
const message = buildBinaryMessage({
type,
streamId,
sequence,
flags: flags | MessageFlags.FIN,
data: processedData,
});
await this.emitMessage(type, streamId, message);
}
async sendStreaming(type, streamId, data) {
if (!this.streamManager) {
return this.sendDirect(type, streamId, data, 0);
}
if (this.flowController) {
this.flowController.initializeStream(streamId);
}
let processedData = data;
if (this.shouldUseCompression(type)) {
processedData = await (0, compression_1.compressBuffer)(processedData, this.compressionAlgorithm);
}
const chunks = this.streamManager.splitIntoChunks(processedData, this.chunkSize);
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const isLast = i === chunks.length - 1;
if (this.flowController) {
while (!this.flowController.canSend(streamId, chunk.length)) {
await new Promise(resolve => setTimeout(resolve, 50));
}
this.flowController.send(streamId, chunk.length);
}
const sequence = i;
const flags = isLast ? MessageFlags.FIN : 0;
const message = buildBinaryMessage({
type,
streamId,
sequence,
flags,
data: chunk,
});
await this.emitMessage(type, streamId, message);
}
}
async emitMessage(type, streamId, message) {
const base64Data = message.toString('base64');
if (type === MessageType.HTTP_REQUEST) {
this.socket.emit('request', {
id: streamId,
data: base64Data,
});
}
else if (type === MessageType.HTTP_RESPONSE) {
this.socket.emit('response', {
id: streamId,
data: base64Data,
});
}
else if (type === MessageType.TCP_DATA) {
this.socket.emit('tcp:data', {
streamId: streamId,
data: base64Data,
});
}
}
async sendHTTPRequest(id, data) {
await this.buildAndSendMessage(MessageType.HTTP_REQUEST, id, data);
}
async sendHTTPResponse(id, data) {
await this.buildAndSendMessage(MessageType.HTTP_RESPONSE, id, data);
}
async sendTCPData(streamId, data) {
await this.buildAndSendMessage(MessageType.TCP_DATA, streamId, data, 0);
}
onHTTPRequest(handler) {
this.httpRequestHandler = handler;
}
onHTTPResponse(handler) {
this.httpResponseHandler = handler;
}
onTCPData(handler) {
this.tcpDataHandlers.add(handler);
return () => this.tcpDataHandlers.delete(handler);
}
}
exports.NewProtocolAdapter = NewProtocolAdapter;
class ProtocolAdapterFactory {
static create(socket, capabilities, isClient = false) {
if (capabilities) {
return new NewProtocolAdapter(socket, capabilities, isClient);
}
else {
return new LegacyProtocolAdapter(socket, isClient);
}
}
}
exports.ProtocolAdapterFactory = ProtocolAdapterFactory;