{"version":3,"file":"framing.d.ts","sourceRoot":"","sources":["../src/framing.ts"],"names":[],"mappings":"AAIA,uDAAuD;AACvD,eAAO,MAAM,wBAAwB,QAAmB,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,UAAW,SAAQ,KAAK;IACpC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACD;AAUD,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU,CAW3D;AAID,gFAAgF;AAChF,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,mBAAmB,CAAyB;IACpD,OAAO,CAAC,yBAAyB,CAAK;IACtC,OAAO,CAAC,qBAAqB,CAAqB;IAClD,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAwB;IAErC,YAAY,OAAO,CAAC,EAAE,mBAAmB,EAExC;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,CAuEpC;IAED,GAAG,IAAI,IAAI,CAOV;IAED,OAAO,CAAC,IAAI;CAUZ","sourcesContent":["const FRAME_HEADER_LENGTH = 4;\nconst MAX_UINT32 = 0xffff_ffff;\nconst PAYLOAD_BLOCK_SIZE = 64 * 1024;\n\n/** Default upper bound for one framed CBOR payload. */\nexport const DEFAULT_MAX_FRAME_LENGTH = 16 * 1024 * 1024;\n\nexport interface FrameDecoderOptions {\n\tmaxFrameLength?: number;\n}\n\nexport class FrameError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"FrameError\";\n\t}\n}\n\nfunction resolveMaxFrameLength(options: FrameDecoderOptions | undefined): number {\n\tconst value = options?.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(value) || value < 0 || value > MAX_UINT32) {\n\t\tthrow new RangeError(`maxFrameLength must be an integer between 0 and ${MAX_UINT32}`);\n\t}\n\treturn value;\n}\n\n/** Prefixes a payload with its unsigned 32-bit big-endian byte length. */\nexport function encodeFrame(payload: Uint8Array): Uint8Array {\n\tif (!(payload instanceof Uint8Array)) throw new TypeError(\"Frame payload must be a Uint8Array\");\n\tif (payload.byteLength > MAX_UINT32) throw new RangeError(\"Frame payload exceeds the unsigned 32-bit length limit\");\n\tconst frame = new Uint8Array(FRAME_HEADER_LENGTH + payload.byteLength);\n\tconst length = payload.byteLength;\n\tframe[0] = length >>> 24;\n\tframe[1] = length >>> 16;\n\tframe[2] = length >>> 8;\n\tframe[3] = length;\n\tframe.set(payload, FRAME_HEADER_LENGTH);\n\treturn frame;\n}\n\ntype DecoderState = \"open\" | \"ended\" | \"failed\";\n\n/** Incrementally splits arbitrary byte chunks into length-prefixed payloads. */\nexport class FrameDecoder {\n\tprivate readonly header = new Uint8Array(FRAME_HEADER_LENGTH);\n\tprivate headerLength = 0;\n\tprivate readonly maxFrameLength: number;\n\tprivate payloadBlocks: Uint8Array[] = [];\n\tprivate currentPayloadBlock: Uint8Array | undefined;\n\tprivate currentPayloadBlockLength = 0;\n\tprivate expectedPayloadLength: number | undefined;\n\tprivate payloadLength = 0;\n\tprivate state: DecoderState = \"open\";\n\n\tconstructor(options?: FrameDecoderOptions) {\n\t\tthis.maxFrameLength = resolveMaxFrameLength(options);\n\t}\n\n\tpush(chunk: Uint8Array): Uint8Array[] {\n\t\tif (this.state === \"ended\") throw new FrameError(\"Frame decoder has ended\");\n\t\tif (this.state === \"failed\") throw new FrameError(\"Frame decoder has failed\");\n\t\tif (!(chunk instanceof Uint8Array)) throw new TypeError(\"Frame chunk must be a Uint8Array\");\n\n\t\tconst frames: Uint8Array[] = [];\n\t\tlet chunkOffset = 0;\n\t\twhile (chunkOffset < chunk.byteLength) {\n\t\t\tif (this.expectedPayloadLength === undefined) {\n\t\t\t\tconst headerBytes = Math.min(FRAME_HEADER_LENGTH - this.headerLength, chunk.byteLength - chunkOffset);\n\t\t\t\tthis.header.set(chunk.subarray(chunkOffset, chunkOffset + headerBytes), this.headerLength);\n\t\t\t\tthis.headerLength += headerBytes;\n\t\t\t\tchunkOffset += headerBytes;\n\t\t\t\tif (this.headerLength < FRAME_HEADER_LENGTH) continue;\n\n\t\t\t\tconst frameLength =\n\t\t\t\t\tthis.header[0]! * 0x1_000_000 + this.header[1]! * 0x1_0000 + this.header[2]! * 0x100 + this.header[3]!;\n\t\t\t\tthis.headerLength = 0;\n\t\t\t\tif (frameLength > this.maxFrameLength) {\n\t\t\t\t\tthis.fail(`Frame length ${frameLength} exceeds configured limit of ${this.maxFrameLength}`);\n\t\t\t\t}\n\t\t\t\tif (frameLength === 0) {\n\t\t\t\t\tframes.push(new Uint8Array());\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis.expectedPayloadLength = frameLength;\n\t\t\t\tthis.payloadBlocks = [];\n\t\t\t\tthis.currentPayloadBlock = undefined;\n\t\t\t\tthis.currentPayloadBlockLength = 0;\n\t\t\t\tthis.payloadLength = 0;\n\t\t\t}\n\n\t\t\tconst expectedPayloadLength = this.expectedPayloadLength;\n\t\t\tif (expectedPayloadLength === undefined) continue;\n\t\t\twhile (chunkOffset < chunk.byteLength && this.payloadLength < expectedPayloadLength) {\n\t\t\t\tlet block = this.currentPayloadBlock;\n\t\t\t\tif (!block || this.currentPayloadBlockLength === block.byteLength) {\n\t\t\t\t\tblock = new Uint8Array(Math.min(PAYLOAD_BLOCK_SIZE, expectedPayloadLength - this.payloadLength));\n\t\t\t\t\tthis.payloadBlocks.push(block);\n\t\t\t\t\tthis.currentPayloadBlock = block;\n\t\t\t\t\tthis.currentPayloadBlockLength = 0;\n\t\t\t\t}\n\t\t\t\tconst payloadBytes = Math.min(\n\t\t\t\t\tblock.byteLength - this.currentPayloadBlockLength,\n\t\t\t\t\tchunk.byteLength - chunkOffset,\n\t\t\t\t);\n\t\t\t\tblock.set(chunk.subarray(chunkOffset, chunkOffset + payloadBytes), this.currentPayloadBlockLength);\n\t\t\t\tthis.currentPayloadBlockLength += payloadBytes;\n\t\t\t\tthis.payloadLength += payloadBytes;\n\t\t\t\tchunkOffset += payloadBytes;\n\t\t\t}\n\t\t\tif (this.payloadLength === expectedPayloadLength) {\n\t\t\t\tif (this.payloadBlocks.length === 1) {\n\t\t\t\t\tframes.push(this.payloadBlocks[0]!);\n\t\t\t\t} else {\n\t\t\t\t\tconst payload = new Uint8Array(expectedPayloadLength);\n\t\t\t\t\tlet offset = 0;\n\t\t\t\t\tfor (const payloadBlock of this.payloadBlocks) {\n\t\t\t\t\t\tpayload.set(payloadBlock, offset);\n\t\t\t\t\t\toffset += payloadBlock.byteLength;\n\t\t\t\t\t}\n\t\t\t\t\tframes.push(payload);\n\t\t\t\t}\n\t\t\t\tthis.payloadBlocks = [];\n\t\t\t\tthis.currentPayloadBlock = undefined;\n\t\t\t\tthis.currentPayloadBlockLength = 0;\n\t\t\t\tthis.expectedPayloadLength = undefined;\n\t\t\t\tthis.payloadLength = 0;\n\t\t\t}\n\t\t}\n\t\treturn frames;\n\t}\n\n\tend(): void {\n\t\tif (this.state === \"ended\") throw new FrameError(\"Frame decoder has ended\");\n\t\tif (this.state === \"failed\") throw new FrameError(\"Frame decoder has failed\");\n\t\tif (this.headerLength !== 0 || this.expectedPayloadLength !== undefined) {\n\t\t\tthis.fail(\"Truncated frame at end of stream\");\n\t\t}\n\t\tthis.state = \"ended\";\n\t}\n\n\tprivate fail(message: string): never {\n\t\tthis.state = \"failed\";\n\t\tthis.headerLength = 0;\n\t\tthis.payloadBlocks = [];\n\t\tthis.currentPayloadBlock = undefined;\n\t\tthis.currentPayloadBlockLength = 0;\n\t\tthis.expectedPayloadLength = undefined;\n\t\tthis.payloadLength = 0;\n\t\tthrow new FrameError(message);\n\t}\n}\n"]}