/** * 协议帧编解码基座(1.4.11)。 * * 帧格式(小端): * [0..3] uint32 整帧长度(含头,6 + payload) * [4..5] uint16 序号 * [6..] bytes 载荷 * * 该格式与 MySQL / MariaDB 的 3 字节长度头不同,是引擎无关的内部封装层: * 各引擎协议实现(1.4.3–1.4.10)在载荷里携带自己的报文,本层只负责 * 传输分帧、粘包拆分与错误长度防御。 */ import { ConnectionError } from '../connection/types'; /** 一帧数据。 */ export class Frame { readonly seq: number; readonly payload: Uint8Array; constructor(seq: number, payload: Uint8Array) { this.seq = seq; this.payload = payload; } } export class FrameCodec { /** 头长度:4 字节长度 + 2 字节序号。 */ static readonly HEADER_BYTES: number = 6; /** 单帧载荷上限:16 MB。 */ static readonly MAX_PAYLOAD_BYTES: number = 16 * 1024 * 1024; /** 组帧。载荷超限时抛 ConnectionError。 */ static encode(seq: number, payload: Uint8Array): Uint8Array { if (payload.length > FrameCodec.MAX_PAYLOAD_BYTES) { throw new ConnectionError('payload too large: ' + payload.length); } const total: number = FrameCodec.HEADER_BYTES + payload.length; const out: Uint8Array = new Uint8Array(total); out[0] = total & 0xFF; out[1] = (total >>> 8) & 0xFF; out[2] = (total >>> 16) & 0xFF; out[3] = (total >>> 24) & 0xFF; out[4] = seq & 0xFF; out[5] = (seq >>> 8) & 0xFF; out.set(payload, FrameCodec.HEADER_BYTES); return out; } /** 读取头部声明的整帧长度;不足 4 字节返回 -1。 */ static frameLength(header: Uint8Array): number { if (header.length < 4) { return -1; } return header[0] | (header[1] << 8) | (header[2] << 16) | (header[3] << 24); } /** 解出第一帧;数据不足返回 undefined(由调用方继续收)。 */ static decode(buffer: Uint8Array): Frame | undefined { const total: number = FrameCodec.frameLength(buffer); if (total < 0) { return undefined; } if (total < FrameCodec.HEADER_BYTES || total > FrameCodec.HEADER_BYTES + FrameCodec.MAX_PAYLOAD_BYTES) { throw new ConnectionError('invalid frame length: ' + total); } if (buffer.length < total) { return undefined; } const seq: number = buffer[4] | (buffer[5] << 8); return new Frame(seq, buffer.slice(FrameCodec.HEADER_BYTES, total)); } }