返回源码地图

packages/protocol/src/framing.ts

v1.0.0 · a13d35a742c6 · 10:帧契约浏览;非全部 CBOR 编解码审计

完整原文供逐行核对;页面收录不代表每行都经过人工语义审核。MIT 许可见 许可证。

1const FRAME_HEADER_LENGTH = 4;
2const MAX_UINT32 = 0xffff_ffff;
3const PAYLOAD_BLOCK_SIZE = 64 * 1024;
4
5/** Default upper bound for one framed CBOR payload. */
6export const DEFAULT_MAX_FRAME_LENGTH = 16 * 1024 * 1024;
7
8export interface FrameDecoderOptions {
9 maxFrameLength?: number;
10}
11
12export class FrameError extends Error {
13 constructor(message: string) {
14 super(message);
15 this.name = "FrameError";
16 }
17}
18
19function resolveMaxFrameLength(options: FrameDecoderOptions | undefined): number {
20 const value = options?.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;
21 if (!Number.isSafeInteger(value) || value < 0 || value > MAX_UINT32) {
22 throw new RangeError(`maxFrameLength must be an integer between 0 and ${MAX_UINT32}`);
23 }
24 return value;
25}
26
27/** Prefixes a payload with its unsigned 32-bit big-endian byte length. */
28export function encodeFrame(payload: Uint8Array): Uint8Array {
29 if (!(payload instanceof Uint8Array)) throw new TypeError("Frame payload must be a Uint8Array");
30 if (payload.byteLength > MAX_UINT32) throw new RangeError("Frame payload exceeds the unsigned 32-bit length limit");
31 const frame = new Uint8Array(FRAME_HEADER_LENGTH + payload.byteLength);
32 const length = payload.byteLength;
33 frame[0] = length >>> 24;
34 frame[1] = length >>> 16;
35 frame[2] = length >>> 8;
36 frame[3] = length;
37 frame.set(payload, FRAME_HEADER_LENGTH);
38 return frame;
39}
40
41type DecoderState = "open" | "ended" | "failed";
42
43/** Incrementally splits arbitrary byte chunks into length-prefixed payloads. */
44export class FrameDecoder {
45 private readonly header = new Uint8Array(FRAME_HEADER_LENGTH);
46 private headerLength = 0;
47 private readonly maxFrameLength: number;
48 private payloadBlocks: Uint8Array[] = [];
49 private currentPayloadBlock: Uint8Array | undefined;
50 private currentPayloadBlockLength = 0;
51 private expectedPayloadLength: number | undefined;
52 private payloadLength = 0;
53 private state: DecoderState = "open";
54
55 constructor(options?: FrameDecoderOptions) {
56 this.maxFrameLength = resolveMaxFrameLength(options);
57 }
58
59 push(chunk: Uint8Array): Uint8Array[] {
60 if (this.state === "ended") throw new FrameError("Frame decoder has ended");
61 if (this.state === "failed") throw new FrameError("Frame decoder has failed");
62 if (!(chunk instanceof Uint8Array)) throw new TypeError("Frame chunk must be a Uint8Array");
63
64 const frames: Uint8Array[] = [];
65 let chunkOffset = 0;
66 while (chunkOffset < chunk.byteLength) {
67 if (this.expectedPayloadLength === undefined) {
68 const headerBytes = Math.min(FRAME_HEADER_LENGTH - this.headerLength, chunk.byteLength - chunkOffset);
69 this.header.set(chunk.subarray(chunkOffset, chunkOffset + headerBytes), this.headerLength);
70 this.headerLength += headerBytes;
71 chunkOffset += headerBytes;
72 if (this.headerLength < FRAME_HEADER_LENGTH) continue;
73
74 const frameLength =
75 this.header[0]! * 0x1_000_000 + this.header[1]! * 0x1_0000 + this.header[2]! * 0x100 + this.header[3]!;
76 this.headerLength = 0;
77 if (frameLength > this.maxFrameLength) {
78 this.fail(`Frame length ${frameLength} exceeds configured limit of ${this.maxFrameLength}`);
79 }
80 if (frameLength === 0) {
81 frames.push(new Uint8Array());
82 continue;
83 }
84 this.expectedPayloadLength = frameLength;
85 this.payloadBlocks = [];
86 this.currentPayloadBlock = undefined;
87 this.currentPayloadBlockLength = 0;
88 this.payloadLength = 0;
89 }
90
91 const expectedPayloadLength = this.expectedPayloadLength;
92 if (expectedPayloadLength === undefined) continue;
93 while (chunkOffset < chunk.byteLength && this.payloadLength < expectedPayloadLength) {
94 let block = this.currentPayloadBlock;
95 if (!block || this.currentPayloadBlockLength === block.byteLength) {
96 block = new Uint8Array(Math.min(PAYLOAD_BLOCK_SIZE, expectedPayloadLength - this.payloadLength));
97 this.payloadBlocks.push(block);
98 this.currentPayloadBlock = block;
99 this.currentPayloadBlockLength = 0;
100 }
101 const payloadBytes = Math.min(
102 block.byteLength - this.currentPayloadBlockLength,
103 chunk.byteLength - chunkOffset,
104 );
105 block.set(chunk.subarray(chunkOffset, chunkOffset + payloadBytes), this.currentPayloadBlockLength);
106 this.currentPayloadBlockLength += payloadBytes;
107 this.payloadLength += payloadBytes;
108 chunkOffset += payloadBytes;
109 }
110 if (this.payloadLength === expectedPayloadLength) {
111 if (this.payloadBlocks.length === 1) {
112 frames.push(this.payloadBlocks[0]!);
113 } else {
114 const payload = new Uint8Array(expectedPayloadLength);
115 let offset = 0;
116 for (const payloadBlock of this.payloadBlocks) {
117 payload.set(payloadBlock, offset);
118 offset += payloadBlock.byteLength;
119 }
120 frames.push(payload);
121 }
122 this.payloadBlocks = [];
123 this.currentPayloadBlock = undefined;
124 this.currentPayloadBlockLength = 0;
125 this.expectedPayloadLength = undefined;
126 this.payloadLength = 0;
127 }
128 }
129 return frames;
130 }
131
132 end(): void {
133 if (this.state === "ended") throw new FrameError("Frame decoder has ended");
134 if (this.state === "failed") throw new FrameError("Frame decoder has failed");
135 if (this.headerLength !== 0 || this.expectedPayloadLength !== undefined) {
136 this.fail("Truncated frame at end of stream");
137 }
138 this.state = "ended";
139 }
140
141 private fail(message: string): never {
142 this.state = "failed";
143 this.headerLength = 0;
144 this.payloadBlocks = [];
145 this.currentPayloadBlock = undefined;
146 this.currentPayloadBlockLength = 0;
147 this.expectedPayloadLength = undefined;
148 this.payloadLength = 0;
149 throw new FrameError(message);
150 }
151}