netkitty
Version:
Network tools kit
165 lines (152 loc) • 7.12 kB
text/typescript
import {CodecModuleConstructor} from './types/CodecModuleConstructor'
import {CodecModule} from './types/CodecModule'
import {CodecDecodeResult} from './types/CodecDecodeResult'
import {CodecEncodeInput} from './types/CodecEncodeInput'
import {CodecErrorInfo} from './types/CodecErrorInfo'
import {PostHandlerItem} from './types/PostHandlerItem'
import {CodecData} from './types/CodecData'
import {NoAvailableCodecError} from '../../errors/NoAvailableCodecError'
import {FlexibleObject} from './lib/FlexibleObject'
import {CodecSchema} from './types/CodecSchema'
import {ProcessPacketDecodePostHandlers, ProcessPacketEncodePostHandlers} from './lib/ProcessPacketPostHandlers'
import {CodecEncodeResult} from './types/CodecEncodeResult'
import {RawData} from './headers/RawData'
import * as packetHeaders from './PacketHeaders'
export class Codec {
readonly #codecModuleConstructors: CodecModuleConstructor[] = []
readonly #codecSchemas: CodecSchema[] = []
protected get HEADER_CODECS(): CodecModuleConstructor[] {
return this.#codecModuleConstructors
}
public get CODEC_SCHEMAS(): CodecSchema[] {
return this.#codecSchemas
}
constructor(codecModuleConstructors: CodecModuleConstructor[] = []) {
this.#codecModuleConstructors = this.loadHeaderCodecs()
if (codecModuleConstructors) {
const replaced: CodecModuleConstructor[] = []
codecModuleConstructors.forEach((codecModuleConstructor: CodecModuleConstructor) => {
const id: string = codecModuleConstructor.PROTOCOL_ID
this.#codecModuleConstructors.forEach((HEADER_CODEC: CodecModuleConstructor, index: number, array: CodecModuleConstructor[]): void => {
if (HEADER_CODEC.PROTOCOL_ID === id) {
array[index] = codecModuleConstructor
replaced.push(codecModuleConstructor)
}
})
})
codecModuleConstructors
.filter((codecModuleConstructor: CodecModuleConstructor): boolean => !replaced.includes(codecModuleConstructor))
.forEach((codecModuleConstructor: CodecModuleConstructor): number => this.#codecModuleConstructors.push(codecModuleConstructor))
}
this.#codecSchemas = this.loadCodecSchemas()
}
/**
* Load codec schemas
* @protected
*/
protected loadCodecSchemas(): CodecSchema[] {
return this.HEADER_CODECS.map((codecModuleConstructor: CodecModuleConstructor): CodecSchema => ({
id: codecModuleConstructor.PROTOCOL_ID,
name: codecModuleConstructor.PROTOCOL_NAME,
schema: codecModuleConstructor.PROTOCOL_SCHEMA
}))
}
/**
* Load header codecs
* @protected
*/
protected loadHeaderCodecs(): CodecModuleConstructor[] {
let headerCodecs: CodecModuleConstructor[] = [...Object.values(packetHeaders)]
headerCodecs = headerCodecs.filter((codec: CodecModuleConstructor): boolean => codec.PROTOCOL_ID !== RawData.PROTOCOL_ID)
//Ensure RawData codec is in the end of header codecs
headerCodecs.push(RawData)
return headerCodecs
}
/**
* Internal encode headers to packet
* @param inputs
* @param errors
* @private
*/
async #encode(inputs: CodecEncodeInput[], errors: CodecErrorInfo[] = []): Promise<CodecData> {
const codecData: CodecData = {
packet: Buffer.from([]),
startPos: 0,
postHandlers: []
}
const codecModules: CodecModule[] = []
for (const input of inputs) {
const codecModuleConstructor: CodecModuleConstructor | undefined = this.HEADER_CODECS.find((codec: CodecModuleConstructor): boolean => codec.PROTOCOL_ID === input.id)
if (!codecModuleConstructor) continue
const codecModule: CodecModule = codecModuleConstructor.CREATE_INSTANCE(codecData, codecModules)
codecModule.instance = new FlexibleObject(codecModule.validate(input.data))
await codecModule.encode()
codecModule.errors.forEach((errorInfo: CodecErrorInfo): number => errors.push(errorInfo))
codecData.startPos = codecModule.endPos
codecModules.push(codecModule)
}
return codecData
}
/**
* Internal decode packet
* @param codecData
* @param codecModules
* @private
*/
async #decode(codecData: CodecData, codecModules: CodecModule[] = []): Promise<void> {
const codecModuleConstructor: CodecModuleConstructor | undefined = this.HEADER_CODECS.find((codecModuleConstructor: CodecModuleConstructor): boolean => codecModuleConstructor.MATCH(codecData, codecModules))
//This unavailable error should not be thrown, the raw data codec will always match unknown data successfully
if (!codecModuleConstructor) throw new NoAvailableCodecError('No available codec constructor')
const codecModule: CodecModule = codecModuleConstructor.CREATE_INSTANCE(codecData, codecModules)
await codecModule.decode()
codecData.startPos = codecModule.endPos
codecModules.push(codecModule)
if (codecData.startPos >= codecData.packet.length) return
return this.#decode(codecData, codecModules)
}
/**
* Decode packet
* @param packet
*/
public async decode(packet: Buffer): Promise<CodecDecodeResult[]> {
const codecData: CodecData = {
packet: packet,
startPos: 0,
postHandlers: []
}
const codecModules: CodecModule[] = []
await this.#decode(codecData, codecModules)
const postDecodeHandlers: PostHandlerItem[] = ProcessPacketDecodePostHandlers(codecData.postHandlers)
let postDecodeHandler: PostHandlerItem | undefined = postDecodeHandlers.shift()
while (postDecodeHandler) {
await postDecodeHandler.handler()
postDecodeHandler = postDecodeHandlers.shift()
}
return codecModules.map((codecModule: CodecModule): CodecDecodeResult => ({
id: codecModule.id,
name: codecModule.name,
nickname: codecModule.nickname,
protocol: codecModule.isProtocol,
errors: codecModule.errors,
data: codecModule.instance.getValue()
}))
}
/**
* Encode packet
* @param inputs
*/
public async encode(inputs: CodecEncodeInput[]): Promise<CodecEncodeResult> {
const errors: CodecErrorInfo[] = []
const codecData: CodecData = await this.#encode(inputs, errors)
const postEncodeHandlers: PostHandlerItem[] = ProcessPacketEncodePostHandlers(codecData.postHandlers)
let postEncodeHandler: PostHandlerItem | undefined = postEncodeHandlers.shift()
while (postEncodeHandler) {
await postEncodeHandler.handler()
postEncodeHandler = postEncodeHandlers.shift()
}
return {
packet: codecData.packet,
errors: errors
}
}
}