@litert/televoke
Version:
A simple RPC service framework.
292 lines • 9.79 kB
JavaScript
;
/**
* Copyright 2025 Angus.Fenying <fenying@litert.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TvDecoderV2 = void 0;
const CSv2 = require("./Constants.v2");
const CS = require("../../Constants");
const Errors_1 = require("../../Errors");
const B8 = Buffer.allocUnsafe(8);
const B4 = Buffer.allocUnsafe(4);
const B2 = Buffer.allocUnsafe(2);
function readSmallBytes(bytes, buf, ctx) {
let readBytes = 0;
while (readBytes < bytes) {
if (ctx.chIndex === ctx.chunks.length) {
throw new Errors_1.errors.incomplete_packet();
}
const c = ctx.chunks[ctx.chIndex];
const cLen = c.byteLength;
let cOff = ctx.chOffset;
do {
buf[readBytes++] = c[cOff++];
} while (cOff < cLen && readBytes < bytes);
if (cOff === cLen) {
ctx.chIndex++;
ctx.chOffset = 0;
}
else {
ctx.chOffset = cOff;
}
if (readBytes === bytes) {
break;
}
}
return buf;
}
function readLargeBytes(bytes, ctx) {
let readBytes = 0;
const ret = [];
while (readBytes < bytes) {
if (ctx.chIndex === ctx.chunks.length) {
throw new Errors_1.errors.incomplete_packet();
}
const c = ctx.chunks[ctx.chIndex];
const bytes2Read = Math.min(bytes - readBytes, c.byteLength - ctx.chOffset);
ret.push(c.subarray(ctx.chOffset, ctx.chOffset + bytes2Read));
readBytes += bytes2Read;
ctx.chOffset += bytes2Read;
if (ctx.chOffset === c.byteLength) {
ctx.chIndex++;
ctx.chOffset = 0;
}
if (readBytes === bytes) {
break;
}
}
return ret;
}
function readString(bytes, ctx) {
const td = new TextDecoder('utf8');
let readBytes = 0;
let ret = '';
while (readBytes < bytes) {
if (ctx.chIndex === ctx.chunks.length) {
throw new Errors_1.errors.incomplete_packet();
}
const c = ctx.chunks[ctx.chIndex];
const bytes2Read = Math.min(bytes - readBytes, c.byteLength - ctx.chOffset);
ret += td.decode(c.subarray(ctx.chOffset, ctx.chOffset + bytes2Read), { stream: true });
readBytes += bytes2Read;
ctx.chOffset += bytes2Read;
if (ctx.chOffset === c.byteLength) {
ctx.chIndex++;
ctx.chOffset = 0;
}
if (readBytes === bytes) {
break;
}
}
return ret;
}
function readVarString(ctx) {
const len = readSmallBytes(2, B2, ctx).readUInt16LE(0);
return readString(len, ctx);
}
function readVarBuffer(ctx) {
const len = readSmallBytes(4, B4, ctx).readUInt32LE(0);
return readLargeBytes(len, ctx);
}
function readVarBuffer16(ctx) {
const len = readSmallBytes(2, B4, ctx).readUInt16LE(0);
return readLargeBytes(len, ctx);
}
class TvErrorResponseDecoder {
decode(ctx) {
const len = readSmallBytes(2, B2, ctx).readUInt16LE(0);
const errorMsg = readString(len, ctx);
let err;
if (errorMsg.startsWith(CS.PROTOCOL_ERROR_NAMESPACE)) {
const errMsg = errorMsg.slice(CS.PROTOCOL_ERROR_NAMESPACE.length + 1);
if (errMsg in Errors_1.errors) {
err = new Errors_1.errors[errMsg]();
}
else {
err = new Errors_1.ProtocolError(errMsg, null, null);
}
}
else {
err = new Errors_1.errors.app_error(errorMsg.slice(CS.APP_ERROR_NAMESPACE.length + 1), null);
}
return {
'cmd': ctx.command,
'typ': CSv2.EPacketType.ERROR_RESPONSE,
'seq': ctx.seq,
'ct': err,
};
}
}
class TvApiRequestDecoder {
decode(ctx) {
const name = readVarString(ctx);
return {
'cmd': CSv2.ECommand.API_CALL,
'typ': CSv2.EPacketType.REQUEST,
'seq': ctx.seq,
'ct': {
'name': name,
'body': readVarBuffer(ctx),
}
};
}
}
class TvPingRequestDecoder {
decode(ctx) {
return {
'cmd': CSv2.ECommand.PING,
'typ': CSv2.EPacketType.REQUEST,
'seq': ctx.seq,
'ct': readVarBuffer16(ctx)
};
}
}
class TvPushMessageRequestDecoder {
decode(ctx) {
return {
'cmd': CSv2.ECommand.PUSH_MESSAGE,
'typ': CSv2.EPacketType.REQUEST,
'seq': ctx.seq,
'ct': readVarBuffer(ctx)
};
}
}
class TvBinaryChunkRequestDecoder {
decode(ctx) {
const streamId = readSmallBytes(4, B4, ctx).readUInt32LE(0);
const chunkIndex = readSmallBytes(4, B4, ctx).readUInt32LE(0);
return {
'cmd': CSv2.ECommand.BINARY_CHUNK,
'typ': CSv2.EPacketType.REQUEST,
'seq': ctx.seq,
'ct': {
'streamId': streamId,
'index': chunkIndex,
'body': readVarBuffer(ctx),
}
};
}
}
class TvCloseRequestDecoder {
decode(ctx) {
return {
'cmd': CSv2.ECommand.CLOSE,
'typ': CSv2.EPacketType.REQUEST,
'seq': ctx.seq,
'ct': null
};
}
}
class TvVoidResponseDecoder {
decode(ctx) {
return {
'cmd': ctx.command,
'typ': CSv2.EPacketType.SUCCESS_RESPONSE,
'seq': ctx.seq,
'ct': null
};
}
}
class TvApiResponseDecoder {
decode(ctx) {
return {
'cmd': CSv2.ECommand.API_CALL,
'typ': CSv2.EPacketType.SUCCESS_RESPONSE,
'seq': ctx.seq,
'ct': readVarBuffer(ctx)
};
}
}
class TvPingResponseDecoder {
decode(ctx) {
return {
'cmd': CSv2.ECommand.PING,
'typ': CSv2.EPacketType.SUCCESS_RESPONSE,
'seq': ctx.seq,
'ct': readVarBuffer16(ctx)
};
}
}
const packetDecoders = {
[CSv2.EPacketType.ERROR_RESPONSE + 0x100 * CSv2.ECommand.API_CALL]: new TvErrorResponseDecoder(),
[CSv2.EPacketType.ERROR_RESPONSE + 0x100 * CSv2.ECommand.PING]: new TvErrorResponseDecoder(),
[CSv2.EPacketType.ERROR_RESPONSE + 0x100 * CSv2.ECommand.BINARY_CHUNK]: new TvErrorResponseDecoder(),
[CSv2.EPacketType.ERROR_RESPONSE + 0x100 * CSv2.ECommand.CLOSE]: new TvErrorResponseDecoder(),
[CSv2.EPacketType.ERROR_RESPONSE + 0x100 * CSv2.ECommand.PUSH_MESSAGE]: new TvErrorResponseDecoder(),
[CSv2.EPacketType.REQUEST + 0x100 * CSv2.ECommand.API_CALL]: new TvApiRequestDecoder(),
[CSv2.EPacketType.REQUEST + 0x100 * CSv2.ECommand.PING]: new TvPingRequestDecoder(),
[CSv2.EPacketType.REQUEST + 0x100 * CSv2.ECommand.PUSH_MESSAGE]: new TvPushMessageRequestDecoder(),
[CSv2.EPacketType.REQUEST + 0x100 * CSv2.ECommand.CLOSE]: new TvCloseRequestDecoder(),
[CSv2.EPacketType.REQUEST + 0x100 * CSv2.ECommand.BINARY_CHUNK]: new TvBinaryChunkRequestDecoder(),
[CSv2.EPacketType.SUCCESS_RESPONSE + 0x100 * CSv2.ECommand.PUSH_MESSAGE]: new TvVoidResponseDecoder(),
[CSv2.EPacketType.SUCCESS_RESPONSE + 0x100 * CSv2.ECommand.CLOSE]: new TvVoidResponseDecoder(),
[CSv2.EPacketType.SUCCESS_RESPONSE + 0x100 * CSv2.ECommand.BINARY_CHUNK]: new TvVoidResponseDecoder(),
[CSv2.EPacketType.SUCCESS_RESPONSE + 0x100 * CSv2.ECommand.API_CALL]: new TvApiResponseDecoder(),
[CSv2.EPacketType.SUCCESS_RESPONSE + 0x100 * CSv2.ECommand.PING]: new TvPingResponseDecoder(),
};
class TvDecoderV2 {
/**
* Pass all chunks of a whole packet to this method, and it will return an array of decoded results.
*
* @throws {TelevokeError}
*/
decode(packetChunks) {
const ctx = this._decodeHeader(packetChunks);
try {
return packetDecoders[ctx.command * 0x100 + ctx.type].decode(ctx);
}
catch (e) {
if (e instanceof Errors_1.TelevokeError) {
throw e;
}
throw new Errors_1.errors.invalid_packet(null, e);
}
}
_decodeHeader(chunks) {
try {
const ctx = {
chunks,
chIndex: 0,
chOffset: 0,
command: 0,
type: 0,
seq: 0,
};
readSmallBytes(8, B8, ctx);
ctx.command = B8[0];
ctx.type = B8[1];
if (undefined === CSv2.EPacketType[ctx.type]) {
throw new Errors_1.errors.invalid_packet({
unknownPacketType: ctx.type,
});
}
if (undefined === CSv2.ECommand[ctx.command]) {
throw new Errors_1.errors.invalid_packet({
unknownCommand: ctx.command,
});
}
ctx.seq = B8.readUInt16LE(2) * 4294967296 + B8.readUInt32LE(4);
return ctx;
}
catch (e) {
if (e instanceof Errors_1.TelevokeError) {
throw e;
}
throw new Errors_1.errors.invalid_packet(null, e);
}
}
}
exports.TvDecoderV2 = TvDecoderV2;
//# sourceMappingURL=DecoderV2.js.map