@undercroft/pulse-frame
Version:
Low-level WebSocket framing library implementing RFC 6455. Supports payload masking, control frames, and binary/text handling.
1 lines • 49.7 kB
Source Map (JSON)
{"version":3,"sources":["../src/mask-payload/index.ts","../src/types/pulse-frame-opcode.ts","../src/types/pulse-frame.ts","../src/create-frame/index.ts","../src/types/pulse-frame-error.ts","../src/extract-application-data/index.ts","../src/extract-masking-key/index.ts","../src/extract-payload/index.ts","../src/parse-extended-length-offset/index.ts","../src/parse-header/index.ts","../src/unmask-payload/index.ts","../src/utils/utf8.ts","../src/validate-close-frame-payload/index.ts","../src/validate-extensions/index.ts","../src/validate-op-code/index.ts","../src/read-payload-length/index.ts","../src/extract-extension-data/index.ts","../src/create-frame-from-buffer/index.ts","../src/create-frame-from-control-code/index.ts","../src/create-frame-from-text/index.ts","../src/calculate-payload-encoding-length/index.ts","../src/create-payload-buffer/index.ts","../src/pack-header-bytes/index.ts","../src/frame-to-buffer/index.ts"],"sourcesContent":["import { MASKING_KEY_LENGTH } from \"@/types/constants\";\n\n/**\n * Applies WebSocket masking (XOR with a 4-byte key) to the given payload.\n *\n * @param payload - The payload to be masked.\n * @param maskingKey - A 4-byte masking key.\n * @returns A new masked Buffer.\n *\n * @see RFC 6455 §5.3 - Masking\n */\nexport function maskPayload(payload: Buffer, maskingKey: Buffer): Buffer {\n const masked = Buffer.allocUnsafe(payload.length);\n for (let i = 0; i < payload.length; i++) {\n masked[i] = payload[i] ^ maskingKey[i % MASKING_KEY_LENGTH];\n }\n return masked;\n};\n","/**\r\n * Enum of all valid WebSocket frame opcodes, defined by RFC 6455 §5.2.\r\n */\r\nexport enum PulseFrameOpcode {\r\n /** Continuation frame for fragmented messages */\r\n CONTINUATION = 0x0,\r\n\r\n /** Text message frame */\r\n TEXT = 0x1,\r\n\r\n /** Binary message frame */\r\n BINARY = 0x2,\r\n\r\n /** Connection close frame */\r\n CLOSE = 0x8,\r\n\r\n /** Ping frame */\r\n PING = 0x9,\r\n\r\n /** Pong frame */\r\n PONG = 0xa,\r\n}","import { OPCODE_CONTROL_THRESHOLD } from './constants';\r\nimport { PulseFrameHeader } from './pulse-frame-header';\r\nimport { PulseFrameOpcode } from './pulse-frame-opcode';\r\n\r\n/**\r\n * Represents a single WebSocket frame, parsed or constructed manually.\r\n *\r\n * A frame is the fundamental unit of data transmission in the WebSocket protocol.\r\n *\r\n * @see [RFC 6455, Section 5.2 - Base Framing Protocol](https://www.rfc-editor.org/rfc/rfc6455#section-5.2)\r\n */\r\nexport class PulseFrame {\r\n private readonly header: PulseFrameHeader;\r\n private readonly payload: Buffer;\r\n private readonly extensionData?: Buffer;\r\n private readonly applicationData?: Buffer;\r\n private readonly masked: boolean;\r\n\r\n constructor(\r\n header: PulseFrameHeader,\r\n payloadData: Buffer,\r\n extensionData?: Buffer,\r\n applicationData?: Buffer,\r\n ) {\r\n this.header = header;\r\n this.payload = payloadData;\r\n this.extensionData = extensionData;\r\n this.applicationData = applicationData;\r\n\r\n // Cache this at construction time\r\n this.masked = header.mask === 1 && !!header.maskingKey;\r\n }\r\n\r\n /**\r\n * Only used internally after parsing to set application-level decoded payload.\r\n */\r\n public setApplicationData(data: Buffer): void {\r\n (this as any).applicationData = data; // Or refactor constructor instead\r\n }\r\n\r\n /**\r\n * Returns the decoded payload (application data if available).\r\n */\r\n public getPayload(): Buffer {\r\n return this.applicationData ?? this.payload;\r\n }\r\n\r\n /**\r\n * Returns the payload as a UTF-8 string, if any.\r\n */\r\n public getPayloadString(): string {\r\n return this.applicationData?.toString('utf8') ?? '';\r\n }\r\n\r\n /**\r\n * Returns the parsed extension data.\r\n */\r\n public getExtensionData(): Buffer | undefined {\r\n return this.extensionData;\r\n }\r\n\r\n /**\r\n * Returns the raw parsed header.\r\n */\r\n public getHeader(): PulseFrameHeader {\r\n return this.header;\r\n }\r\n\r\n /** Whether the payload was masked. */\r\n public isMasked(): boolean {\r\n return this.masked;\r\n }\r\n\r\n /** Whether this is the final frame in a sequence. */\r\n public isFinal(): boolean {\r\n return this.header.fin === 1;\r\n }\r\n\r\n /** Returns the frame's opcode. */\r\n public getOpcode(): PulseFrameOpcode {\r\n return this.header.opcode;\r\n }\r\n\r\n /** Whether this frame contains text data. */\r\n public isText(): boolean {\r\n return this.header.opcode === PulseFrameOpcode.TEXT;\r\n }\r\n\r\n /** Whether this frame contains binary data. */\r\n public isBinary(): boolean {\r\n return this.header.opcode === PulseFrameOpcode.BINARY;\r\n }\r\n\r\n /** Whether this frame continues a fragmented message. */\r\n public isContinuation(): boolean {\r\n return this.header.opcode === PulseFrameOpcode.CONTINUATION;\r\n }\r\n\r\n /** Whether this frame is a control frame. */\r\n public isControl(): boolean {\r\n return this.header.opcode >= OPCODE_CONTROL_THRESHOLD;\r\n }\r\n\r\n /** Whether this frame is a ping. */\r\n public isPing(): boolean {\r\n return this.header.opcode === PulseFrameOpcode.PING;\r\n }\r\n\r\n /** Whether this frame is a pong. */\r\n public isPong(): boolean {\r\n return this.header.opcode === PulseFrameOpcode.PONG;\r\n }\r\n\r\n /** Whether this frame is a connection close. */\r\n public isClose(): boolean {\r\n return this.header.opcode === PulseFrameOpcode.CLOSE;\r\n }\r\n\r\n /** Whether this is a text or binary data frame. */\r\n public isData(): boolean {\r\n return this.isText() || this.isBinary();\r\n }\r\n\r\n /**\r\n * Converts this frame to a plain object for logging or inspection.\r\n */\r\n public toJSON(): Record<string, unknown> {\r\n return {\r\n fin: this.header.fin,\r\n rsv1: this.header.rsv1,\r\n rsv2: this.header.rsv2,\r\n rsv3: this.header.rsv3,\r\n opcode:\r\n PulseFrameOpcode[this.header.opcode] ??\r\n `Unknown(${this.header.opcode})`,\r\n mask: this.header.mask,\r\n length: this.header.length,\r\n isControl: this.isControl(),\r\n isText: this.isText(),\r\n isBinary: this.isBinary(),\r\n isClose: this.isClose(),\r\n isPing: this.isPing(),\r\n isPong: this.isPong(),\r\n isMasked: this.masked,\r\n applicationDataLength: this.applicationData?.length ?? 0,\r\n extensionDataLength: this.extensionData?.length ?? 0,\r\n };\r\n }\r\n\r\n /**\r\n * Returns a short string representation of the frame.\r\n */\r\n public toString(): string {\r\n return `PulseFrame[opcode=${\r\n PulseFrameOpcode[this.header.opcode] ?? this.header.opcode\r\n }, fin=${this.header.fin}, length=${this.header.length}, masked=${\r\n this.masked\r\n }]`;\r\n }\r\n}\r\n","import { maskPayload } from '../mask-payload';\r\nimport { PulseFrame } from '../types/pulse-frame';\r\nimport { PulseFrameBuildOptions } from '../types/pulse-frame-build-options';\r\nimport { PulseFrameHeader } from '../types/pulse-frame-header';\r\n\r\n/**\r\n * Creates a complete `PulseFrame` instance for transmission over a WebSocket connection.\r\n *\r\n * This function builds the frame header and payload according to\r\n * [RFC 6455 §5.2 - Base Framing Protocol](https://datatracker.ietf.org/doc/html/rfc6455#section-5.2),\r\n * ensuring correct encoding of all fields and masking (if required).\r\n *\r\n * ### RFC 6455 Compliance:\r\n *\r\n * - **FIN / RSV1-3 bits**: Set based on options; used for fragmentation and extensions.\r\n * - **Opcode**: Determines the type of frame (text, binary, close, ping, pong).\r\n * - **Masking**:\r\n * - Applied if `mask` is `1` and a `maskingKey` is provided.\r\n * - Required for clients; servers **MUST NOT** mask frames ([RFC §5.1]).\r\n * - **Payload length**:\r\n * - Calculated after masking is applied.\r\n * - Determines how many length bytes will be encoded later in serialization.\r\n *\r\n * This function does **not** serialize the frame; it prepares the internal representation.\r\n * For binary transmission, use `frame.toBuffer()`.\r\n *\r\n * @param options - Frame metadata and payload data.\r\n * @returns A fully prepared {@link PulseFrame} instance with a correct header and (optionally masked) payload.\r\n *\r\n * @example\r\n * ```ts\r\n * const frame = createFrame({\r\n * fin: 1,\r\n * opcode: PulseFrameOpcode.TEXT,\r\n * mask: 1,\r\n * maskingKey: createRandomBytes(4),\r\n * payloadData: Buffer.from(\"Hello\"),\r\n * });\r\n * socket.write(frame.toBuffer());\r\n * ```\r\n *\r\n * @see RFC 6455 §5.2 - Base Framing Protocol\r\n * @see RFC 6455 §5.1 - Client-to-Server Masking\r\n */\r\nexport function createFrame(options: PulseFrameBuildOptions): PulseFrame {\r\n const header: PulseFrameHeader = {\r\n fin: options.fin ?? 1,\r\n rsv1: options.rsv1 ?? 0,\r\n rsv2: options.rsv2 ?? 0,\r\n rsv3: options.rsv3 ?? 0,\r\n mask: options.mask ?? 0,\r\n opcode: options.opcode,\r\n extensionDataLength: options.extensionDataLength ?? 0,\r\n maskingKey: options.maskingKey,\r\n length: 0,\r\n };\r\n\r\n const payloadData = options.payloadData ?? Buffer.alloc(0);\r\n const isMasked = header.mask === 1 && !!header.maskingKey;\r\n const extensionData = options.extensionData ?? undefined;\r\n\r\n const payload = isMasked\r\n ? maskPayload(payloadData, header.maskingKey!)\r\n : payloadData;\r\n\r\n header.length = payload.length;\r\n\r\n return new PulseFrame(header, payload, extensionData, payload);\r\n}\r\n","/* istanbul ignore file */\r\n\r\n/**\r\n * Represents a protocol error encountered while parsing or constructing\r\n * a WebSocket frame.\r\n *\r\n * Used to signal issues such as:\r\n * - Invalid opcodes\r\n * - Malformed payloads\r\n * - Invalid close codes\r\n * - Incomplete or truncated frames\r\n *\r\n * @extends Error\r\n */\r\nexport class PulseFrameError extends Error {\r\n /**\r\n * Creates a new PulseFrameError with a specific error message.\r\n *\r\n * @param message - A descriptive explanation of the framing error.\r\n */\r\n constructor(public readonly message: string) {\r\n super(message);\r\n this.name = 'PulseFrameError';\r\n Object.setPrototypeOf(this, PulseFrameError.prototype);\r\n }\r\n}\r\n","import { PulseFrameError } from \"../types/pulse-frame-error\";\n\n/**\n * Extracts the application-level data from a WebSocket frame's payload buffer,\n * skipping over any extension data that precedes it.\n *\n * @param buffer - The payload buffer (extension + application data).\n * @param extensionDataLength - The number of bytes reserved for extension data.\n * @returns A subarray containing only the application data portion.\n *\n * @throws PulseFrameError if the extension length exceeds the total buffer length.\n *\n * @see RFC 6455 §5.2 - Base Framing Protocol\n */\nexport function extractApplicationData(buffer: Buffer, extensionDataLength: number): Buffer {\n if (extensionDataLength === 0) {\n return buffer;\n }\n\n if (extensionDataLength > buffer.length) {\n throw new PulseFrameError('Invalid application data length: exceeds buffer size');\n }\n\n return buffer.subarray(extensionDataLength);\n};\n","import { PulseFrameError } from '../types/pulse-frame-error';\n\n/**\n * Extracts the 4-byte masking key from a WebSocket frame buffer.\n *\n * According to RFC 6455 §5.3, if the mask bit is set (MASK = 1),\n * the frame MUST include a 4-byte masking key immediately following\n * the payload length field.\n *\n * This function verifies that enough data exists and returns a\n * reference to the masking key without copying data.\n *\n * @param buffer - The full frame buffer.\n * @param start - The byte offset where the masking key starts.\n * @param length - The number of bytes expected (must be 4).\n * @returns A view into the masking key within the buffer.\n *\n * @throws {PulseFrameError} If the buffer does not contain enough bytes.\n *\n * @see RFC 6455 §5.3 - Data Framing\n */\nexport function extractMaskingKey(buffer: Buffer, start: number, length: number): Buffer {\n const maskingKeyEnd = start + length;\n if (buffer.length < maskingKeyEnd) {\n throw new PulseFrameError('Incomplete frame: expected 4 bytes for masking key');\n }\n\n return buffer.subarray(start, maskingKeyEnd);\n}\n","import { PulseFrameError } from \"../types/pulse-frame-error\";\n\n/**\n * Extracts the payload portion of a WebSocket frame from the buffer.\n *\n * This function verifies that the buffer contains enough bytes from the given\n * start offset to read the declared payload length. It returns a view (not a copy)\n * into the original buffer containing the payload data.\n *\n * @param buffer - The full WebSocket frame buffer.\n * @param start - The byte offset where the payload begins.\n * @param length - The expected length of the payload in bytes.\n * @returns A buffer slice representing the payload data.\n *\n * @throws {PulseFrameError} If the buffer does not contain enough bytes to satisfy the payload length.\n *\n * @see RFC 6455 §5.2 - Base Framing Protocol\n */\nexport function extractPayload(buffer: Buffer, start: number, length: number): Buffer {\n const payloadEnd = start + length;\n if (buffer.length < payloadEnd) {\n throw new PulseFrameError('Incomplete frame: not enough bytes for payload');\n }\n return buffer.subarray(start, payloadEnd);\n};\n","import {\n PAYLOAD_EXTENDED_16,\n EXTENDED_LENGTH_16_BYTES,\n PAYLOAD_EXTENDED_64,\n EXTENDED_LENGTH_64_BYTES,\n} from '@/types/constants';\nimport { PulseFrameError } from '../types/pulse-frame-error';\n\n/**\n * Determines the number of extra bytes used to represent the payload length\n * in a WebSocket frame, based on the initial 7-bit length field.\n *\n * @param payloadLen - The value from the 7-bit payload length field.\n * @returns The number of bytes used for the extended payload length (0, 2, or 8).\n *\n * @throws PulseFrameError - If the value is invalid according to RFC 6455.\n *\n * @see RFC 6455 §5.2 - Base Framing Protocol\n */\nexport function parseExtendedLengthOffset(payloadLen: number): number {\n if (payloadLen < PAYLOAD_EXTENDED_16) {\n return 0;\n }\n if (payloadLen === PAYLOAD_EXTENDED_16) {\n return EXTENDED_LENGTH_16_BYTES;\n }\n if (payloadLen === PAYLOAD_EXTENDED_64) {\n return EXTENDED_LENGTH_64_BYTES;\n }\n\n throw new PulseFrameError('Invalid payload length for extended length');\n}\n","import { Bit } from '@/types/bit';\nimport { PulseFrameHeader } from '../types/pulse-frame-header';\nimport { PulseFrameHeaderFlags } from '../types/pulse-frame-header-flags';\n\n/**\n * Parses the first two bytes of a WebSocket frame buffer and extracts the frame header.\n *\n * According to [RFC 6455 §5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.2),\n * the first two bytes of a frame contain:\n *\n * Byte 1:\n * - FIN (1 bit)\n * - RSV1, RSV2, RSV3 (1 bit each)\n * - OPCODE (4 bits)\n *\n * Byte 2:\n * - MASK (1 bit)\n * - Payload Length (7 bits, which may indicate extended length)\n *\n * This function **does not** extract extended payload length or the masking key.\n * It only parses the initial fixed-size portion of the header.\n *\n * @param buffer - The frame buffer to parse (must be at least 2 bytes).\n * @param extensionDataLength - The length of any expected extension data (used downstream).\n * @returns A partially filled `PulseFrameHeader` with core metadata parsed.\n *\n * @throws Will throw if `buffer` is less than 2 bytes long.\n */\nexport function parseHeader(\n buffer: Buffer,\n extensionDataLength: number,\n): PulseFrameHeader {\n const firstByte = buffer[0];\n const secondByte = buffer[1];\n\n return {\n fin: ((firstByte & PulseFrameHeaderFlags.FIN) >>> 7) as Bit,\n rsv1: ((firstByte & PulseFrameHeaderFlags.RSV1) >>> 6) as Bit,\n rsv2: ((firstByte & PulseFrameHeaderFlags.RSV2) >>> 5) as Bit,\n rsv3: ((firstByte & PulseFrameHeaderFlags.RSV3) >>> 4) as Bit,\n mask: ((secondByte & PulseFrameHeaderFlags.MASK) >>> 7) as Bit,\n opcode: firstByte & PulseFrameHeaderFlags.OPCODE,\n length: secondByte & PulseFrameHeaderFlags.LENGTH,\n extensionDataLength,\n maskingKey: undefined,\n };\n}\n","import { MASKING_KEY_LENGTH } from \"@/types/constants\";\n\n/**\n * Unmasks a masked WebSocket payload using the provided masking key.\n *\n * This function reverses the masking applied per [RFC 6455 §5.3](https://datatracker.ietf.org/doc/html/rfc6455#section-5.3), \n * which uses a 4-byte masking key applied cyclically across the payload.\n *\n * Each byte of the payload is XOR’d with a byte from the masking key:\n *\n * ```\n * transformed-octet-i = original-octet-i XOR masking-key-octet-(i mod 4)\n * ```\n *\n * @param payload - The masked payload buffer to unmask.\n * @param maskingKey - The 4-byte masking key used to encode the payload.\n * @returns A new `Buffer` containing the unmasked payload.\n *\n * @remarks\n * - Assumes the caller has already validated that masking is appropriate.\n * - This function does not perform any validation on the length of the masking key.\n * It is assumed to be exactly 4 bytes.\n *\n * @see RFC 6455 §5.3 - Masking\n */\nexport function unmaskPayload(payload: Buffer, maskingKey: Buffer): Buffer {\n const unmasked = Buffer.allocUnsafe(payload.length);\n for (let i = 0; i < payload.length; i++) {\n unmasked[i] = payload[i] ^ maskingKey[i % MASKING_KEY_LENGTH];\n }\n return unmasked;\n}","export const StrictUtf8Decoder = new TextDecoder('utf-8', { fatal: true });\r\n\r\nexport function decodeUtf8Strict(buffer: Buffer): string {\r\n return StrictUtf8Decoder.decode(buffer);\r\n}","import { PulseFrameError } from '../types/pulse-frame-error';\nimport { decodeUtf8Strict } from '@/utils/utf8';\n\n/**\n * Validates the payload of a WebSocket Close frame according to RFC 6455 §5.5.1 and §7.4.\n *\n * A Close frame MAY contain a body, which consists of:\n * - A 2-byte status code (unsigned 16-bit integer, network byte order)\n * - An optional UTF-8 encoded reason string\n *\n * This function enforces:\n * - If a payload is present, it must be 0 bytes, or ≥2 bytes\n * - If ≥2 bytes, the first two bytes must be a valid close code (1000–4999)\n * - If >2 bytes, the remaining bytes must form a valid UTF-8 string\n *\n * Close codes are defined in RFC 6455 §7.4.1 and include:\n * - 1000 (Normal Closure)\n * - 1001–1015 (reserved)\n * - 3000–4999 (application-defined)\n *\n * @param payload - The raw frame payload to validate.\n * @throws {PulseFrameError} If the payload length is 1, the code is invalid, or the reason is not valid UTF-8.\n *\n * @see {@link https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.1}\n * @see {@link https://datatracker.ietf.org/doc/html/rfc6455#section-7.4}\n */\nexport const validateCloseFramePayload = (payload: Buffer): void => {\n if (payload.length === 1) {\n throw new PulseFrameError('Protocol error: close frame payload too small');\n }\n\n if (payload.length >= 2) {\n const code = payload.readUInt16BE(0);\n if (code < 1000 || code > 4999) {\n throw new PulseFrameError(`Protocol error: invalid close code ${code}`);\n }\n }\n\n if (payload.length > 2) {\n const reasonBuffer = payload.subarray(2);\n try {\n decodeUtf8Strict(reasonBuffer);\n } catch {\n throw new PulseFrameError(\n 'Protocol error: invalid UTF-8 in close reason',\n );\n }\n }\n};","import { PulseFrameExtensionSupport } from '../types/pulse-frame-extension-support';\nimport { PulseFrameHeader } from '../types/pulse-frame-header';\n\n/**\n * Validates whether the RSV1, RSV2, and RSV3 bits in a frame header\n * are allowed based on negotiated extension support.\n *\n * @param header - The frame header containing RSV bits.\n * @param options - Optional frame options indicating which RSV flags are supported by extensions.\n *\n * @throws {Error} If any RSV bit is set without an extension supporting it.\n *\n * @remarks\n * Per [RFC 6455 §5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.2), RSV1–RSV3 bits must be zero\n * unless an extension is negotiated that defines meaning for them. This function ensures that any\n * set RSV bit corresponds to an explicitly enabled extension in the frame options.\n *\n * This function is typically called during frame decoding.\n */\nexport function validateExtensions(header: PulseFrameHeader, extensionSupport: PulseFrameExtensionSupport): void {\n if (header.rsv1 === 1 && !extensionSupport.rsv1) {\n throw new Error('RSV1 is set but not supported by any extension');\n }\n\n if (header.rsv2 === 1 && !extensionSupport.rsv2) {\n throw new Error('RSV2 is set but not supported by any extension');\n }\n\n if (header.rsv3 === 1 && !extensionSupport.rsv3) {\n throw new Error('RSV3 is set but not supported by any extension');\n }\n}\n","import {\n MAX_CONTROL_FRAME_PAYLOAD,\n OPCODE_CONTROL_THRESHOLD,\n} from '@/types/constants';\nimport { PulseFrameError } from '../types/pulse-frame-error';\nimport { PulseFrameHeader } from '../types/pulse-frame-header';\nimport { PulseFrameOpcode } from '../types/pulse-frame-opcode';\n\n/**\n * Validates the opcode and framing rules for a WebSocket frame header.\n *\n * According to [RFC 6455 §5.5 - Control Frames](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5):\n *\n * - Control frames (e.g. ping/pong/close) must have `FIN=1` (they must not be fragmented).\n * - Control frames must not exceed 125 bytes in payload length.\n * - All frames must use a valid opcode (0x0–0xA).\n *\n * @throws If the opcode is invalid, the control frame is fragmented, or payload is too long.\n */\nexport function validateOpcode(header: PulseFrameHeader): void {\n const { opcode, fin } = header;\n\n if (!Object.values(PulseFrameOpcode).includes(opcode)) {\n throw new PulseFrameError(`Invalid opcode: ${opcode}`);\n }\n\n const isControlFrame = opcode >= OPCODE_CONTROL_THRESHOLD;\n if (isControlFrame && fin !== 1) {\n throw new PulseFrameError(\n 'Protocol error: control frames must not be fragmented (FIN=1)',\n );\n }\n\n if (isControlFrame && header.length > MAX_CONTROL_FRAME_PAYLOAD) {\n throw new PulseFrameError('Control frame payload too long');\n }\n}\n","import {\r\n PAYLOAD_EXTENDED_16,\r\n EXTENDED_LENGTH_16_BYTES,\r\n PAYLOAD_EXTENDED_64,\r\n EXTENDED_LENGTH_64_BYTES,\r\n} from '@/types/constants';\r\nimport { PulseFrameError } from '../types/pulse-frame-error';\r\n\r\n/**\r\n * Reads the actual payload length from a WebSocket frame buffer.\r\n *\r\n * Handles basic (7-bit), 16-bit extended, and 64-bit extended length fields as per RFC 6455 §5.2.\r\n *\r\n * @param buffer - The buffer containing the WebSocket frame.\r\n * @param payloadLen - The initial payload length value from the header (may be 126 or 127).\r\n * @param offset - The offset at which the extended payload length starts.\r\n * @returns The decoded full payload length.\r\n *\r\n * @throws PulseFrameError - If the buffer is incomplete or the payload length is invalid.\r\n *\r\n * @see RFC 6455 §5.2 - Base Framing Protocol\r\n */\r\nexport function readPayloadLength(\r\n buffer: Buffer,\r\n payloadLen: number,\r\n offset: number,\r\n): number {\r\n // If the length is less than PAYLOAD_EXTENDED_16, return it directly\r\n // as it represents the actual payload length directly.\r\n if (payloadLen < PAYLOAD_EXTENDED_16) {\r\n return payloadLen;\r\n }\r\n\r\n // If the length is PAYLOAD_EXTENDED_16, read the next 2 bytes from the buffer\r\n // to get the actual payload length.\r\n if (payloadLen === PAYLOAD_EXTENDED_16) {\r\n if (buffer.length < offset + EXTENDED_LENGTH_16_BYTES) {\r\n throw new PulseFrameError(\r\n 'Incomplete frame: expected 2 bytes for extended length',\r\n );\r\n }\r\n return buffer.readUInt16BE(offset);\r\n }\r\n\r\n // If the length is PAYLOAD_EXTENDED_64, read the next 8 bytes from the buffer\r\n // to get the actual payload length.\r\n if (payloadLen === PAYLOAD_EXTENDED_64) {\r\n if (buffer.length < offset + EXTENDED_LENGTH_64_BYTES) {\r\n throw new PulseFrameError(\r\n 'Incomplete frame: expected 8 bytes for extended length',\r\n );\r\n }\r\n return Number(buffer.readBigUInt64BE(offset));\r\n }\r\n\r\n // If the length is not one of the expected values, throw an error\r\n throw new PulseFrameError('Invalid payload length');\r\n}\r\n","import { PulseFrameError } from \"../types/pulse-frame-error\";\r\n\r\n/**\r\n * Extracts the extension data segment from a WebSocket frame buffer.\r\n *\r\n * @param buffer - The full frame buffer.\r\n * @param start - The starting offset of the extension data.\r\n * @param length - The length of the extension data.\r\n * @returns A `Buffer` slice containing the extension data.\r\n *\r\n * @throws PulseFrameError if the extension data exceeds the buffer length.\r\n *\r\n * @see RFC 6455 §5.2 - Extension Data\r\n */\r\nexport function extractExtensionData(buffer: Buffer, start: number, length: number): Buffer {\r\n if (length === 0) {\r\n return Buffer.alloc(0);\r\n }\r\n\r\n const end = start + length;\r\n if (end > buffer.length) {\r\n throw new PulseFrameError('Incomplete frame: extension data exceeds buffer length');\r\n }\r\n\r\n return buffer.subarray(start, end);\r\n}\r\n \r\n ","import { extractApplicationData } from '../extract-application-data';\r\nimport { extractMaskingKey } from '../extract-masking-key';\r\nimport { extractPayload } from '../extract-payload';\r\nimport { parseExtendedLengthOffset } from '../parse-extended-length-offset';\r\nimport { parseHeader } from '../parse-header';\r\nimport { PulseFrame } from '../types/pulse-frame';\r\nimport { PulseFrameOptions } from '../types/pulse-frame-options';\r\nimport { unmaskPayload } from '../unmask-payload';\r\nimport { validateCloseFramePayload } from '../validate-close-frame-payload';\r\nimport { validateExtensions } from '../validate-extensions';\r\nimport { validateOpcode } from '../validate-op-code';\r\nimport { PulseFrameOpcode } from '../types/pulse-frame-opcode';\r\nimport { PulseFrameError } from '../types/pulse-frame-error';\r\nimport { readPayloadLength } from '../read-payload-length';\r\nimport { extractExtensionData } from '../extract-extension-data';\r\nimport { HEADER_BASE_LENGTH, MASKING_KEY_LENGTH } from '@/types/constants';\r\n\r\n/**\r\n * Parses a WebSocket frame from a raw buffer according to [RFC 6455 §5](https://datatracker.ietf.org/doc/html/rfc6455#section-5).\r\n *\r\n * This function:\r\n * - Validates header size and structure\r\n * - Extracts masking key, extended payload length, and payload data\r\n * - Validates RSV bits, opcodes, and control frame rules\r\n * - Unmasks payload if required\r\n * - Separates extension data from application data\r\n *\r\n * Supports optional extension handling via {@link PulseFrameOptions}.\r\n *\r\n * @param buffer - The raw incoming buffer containing a WebSocket frame.\r\n * @param options - Optional framing behavior configuration, including extension support and extension data length.\r\n * @returns A parsed and validated {@link PulseFrame} object.\r\n *\r\n * @throws {PulseFrameError} If the frame is incomplete, invalid, or violates protocol constraints.\r\n *\r\n * @example\r\n * ```ts\r\n * const frame = createFrameFromBuffer(buffer, {\r\n * extensionLength: 8,\r\n * extensionSupport: { rsv1: true }\r\n * });\r\n * ```\r\n *\r\n * @see RFC 6455 §5 - Data Framing\r\n * @see RFC 6455 §5.5 - Control Frames\r\n * @see RFC 6455 §7 - Closing Handshake\r\n */\r\nexport function createFrameFromBuffer(\r\n buffer: Buffer,\r\n options?: PulseFrameOptions,\r\n): PulseFrame {\r\n // Fail early if the buffer isn't big enough to have a header\r\n if (buffer.length < HEADER_BASE_LENGTH) {\r\n throw new PulseFrameError('Incomplete frame: missing first two bytes');\r\n }\r\n\r\n // Get the extension data length from options or default to 0\r\n const extensionDataLength = options?.extensionLength ?? 0;\r\n\r\n // Get the extension support from options or default to no extensions\r\n const extensionSupport = options?.extensionSupport ?? {\r\n rsv1: false,\r\n rsv2: false,\r\n rsv3: false,\r\n };\r\n\r\n // Parse the header from the buffer\r\n const frameHeader = parseHeader(buffer, extensionDataLength);\r\n\r\n const isMasked = frameHeader.mask === 1 || !!frameHeader.maskingKey;\r\n\r\n // Validate the frame header extensions\r\n validateExtensions(frameHeader, extensionSupport);\r\n\r\n // Validate the frame header opcode\r\n validateOpcode(frameHeader);\r\n\r\n // Get the payload length from the frame header\r\n const payloadLength = readPayloadLength(\r\n buffer,\r\n frameHeader.length,\r\n HEADER_BASE_LENGTH,\r\n );\r\n\r\n // Parse the extended length offset from the frame header\r\n const extendedLengthOffset = parseExtendedLengthOffset(frameHeader.length);\r\n\r\n // Start reading from the end of the header\r\n if (isMasked) {\r\n const maskingKey = extractMaskingKey(\r\n buffer,\r\n HEADER_BASE_LENGTH + extendedLengthOffset,\r\n MASKING_KEY_LENGTH,\r\n );\r\n frameHeader.maskingKey = maskingKey;\r\n }\r\n\r\n // Determine the read offset based on whether the frame is masked\r\n // and the length of the extended payload.\r\n const readOffset =\r\n HEADER_BASE_LENGTH +\r\n extendedLengthOffset +\r\n (isMasked ? MASKING_KEY_LENGTH : 0);\r\n\r\n // Extract the payload data based on the frame header length\r\n const rawPayloadData = extractPayload(buffer, readOffset, payloadLength);\r\n\r\n // Unmask the payload data if the frame is masked\r\n const payloadData = isMasked\r\n ? unmaskPayload(rawPayloadData, frameHeader.maskingKey!)\r\n : rawPayloadData;\r\n\r\n // Extract the extension data from the payload data\r\n const extensionData = extractExtensionData(\r\n payloadData,\r\n 0,\r\n extensionDataLength,\r\n );\r\n\r\n // Extract the application data from the payload data\r\n const applicationData = extractApplicationData(\r\n payloadData,\r\n extensionDataLength,\r\n );\r\n\r\n if (frameHeader.opcode === PulseFrameOpcode.CLOSE) {\r\n validateCloseFramePayload(applicationData);\r\n }\r\n\r\n return new PulseFrame(\r\n frameHeader,\r\n payloadData,\r\n extensionData,\r\n applicationData,\r\n );\r\n}\r\n","import { PulseFrame } from \"../types/pulse-frame\";\r\nimport { PulseFrameError } from \"../types/pulse-frame-error\";\r\nimport { PulseFrameHeader } from \"../types/pulse-frame-header\";\r\nimport { PulseFrameOpcode } from \"../types/pulse-frame-opcode\";\r\n\r\n/**\r\n * Creates a WebSocket control frame according to [RFC 6455 §5.5](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5).\r\n *\r\n * This utility is used to generate Ping, Pong, and Close frames with the correct header flags:\r\n * - FIN bit is always set (`fin = 1`)\r\n * - RSV1/RSV2/RSV3 are unset (`0`)\r\n * - Control frames must not be fragmented\r\n * - Payload must be ≤ 125 bytes\r\n *\r\n * @param payload - Optional binary payload to include (e.g., Pong echo, Close code+reason).\r\n * @param opcode - The control frame opcode (`PING`, `PONG`, `CLOSE`).\r\n * @returns A fully constructed {@link PulseFrame} representing the control frame.\r\n *\r\n * @throws {PulseFrameError} If the payload exceeds the allowed limit for control frames.\r\n *\r\n * @example\r\n * ```ts\r\n * const pingFrame = createFrameFromControlCode(Buffer.from('ping'), PulseFrameOpcode.PING);\r\n * const closeFrame = createFrameFromControlCode(undefined, PulseFrameOpcode.CLOSE);\r\n * ```\r\n *\r\n * @see RFC 6455 §5.5 - Control Frames\r\n */\r\nexport function createFrameFromControlCode(payload: Buffer | undefined, opcode: PulseFrameOpcode): PulseFrame {\r\n const data = Buffer.isBuffer(payload) ? payload : Buffer.alloc(0);\r\n\r\n if (data.length > 125) {\r\n throw new PulseFrameError('Control frame payload too large (max 125 bytes)');\r\n }\r\n\r\n const header: PulseFrameHeader = {\r\n fin: 1,\r\n rsv1: 0,\r\n rsv2: 0,\r\n rsv3: 0,\r\n mask: 0,\r\n opcode,\r\n length: data.length,\r\n extensionDataLength: 0,\r\n maskingKey: undefined,\r\n };\r\n\r\n return new PulseFrame(header, data, undefined, data);\r\n}\r\n","import { PulseFrame } from '../types/pulse-frame';\r\nimport { PulseFrameHeader } from '../types/pulse-frame-header';\r\nimport { PulseFrameOpcode } from '../types/pulse-frame-opcode';\r\n\r\n/**\r\n * Creates a WebSocket text frame with the given UTF-8 string.\r\n *\r\n * The string is encoded as UTF-8 and inserted directly into the payload.\r\n * This is a convenience wrapper for quickly sending simple text messages.\r\n *\r\n * @param text - The UTF-8 string to send as the frame payload.\r\n * @returns A complete PulseFrame representing a text frame.\r\n *\r\n * @see RFC 6455 §5.6 - Data Frames (Text)\r\n */\r\nexport function createFrameFromText(text: string): PulseFrame {\r\n const payload = Buffer.from(text, 'utf8');\r\n\r\n const header: PulseFrameHeader = {\r\n fin: 1,\r\n rsv1: 0,\r\n rsv2: 0,\r\n rsv3: 0,\r\n mask: 0,\r\n opcode: PulseFrameOpcode.TEXT,\r\n length: payload.length,\r\n extensionDataLength: 0,\r\n maskingKey: undefined,\r\n };\r\n\r\n return new PulseFrame(header, payload, undefined, payload);\r\n}\r\n","import {\r\n EXTENDED_LENGTH_16_BYTES,\r\n EXTENDED_LENGTH_64_BYTES,\r\n MAX_16BIT_PAYLOAD_LENGTH,\r\n PAYLOAD_EXTENDED_16,\r\n} from '@/types/constants';\r\n\r\n/**\r\n * Calculates how many extra bytes are needed to encode the payload length in a WebSocket frame header.\r\n *\r\n * According to [RFC 6455 §5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.2), the payload\r\n * length field is encoded as follows:\r\n *\r\n * - If payload length < 126 → length fits in 7 bits (no extra bytes).\r\n * - If payload length ≥ 126 and < 2^16 → 2 extra bytes are added (16-bit unsigned).\r\n * - If payload length ≥ 2^16 → 8 extra bytes are added (64-bit unsigned).\r\n *\r\n * This function is used during frame construction to determine how many bytes\r\n * must be reserved for length encoding in the final header.\r\n *\r\n * @param payloadLength - The number of payload bytes to encode.\r\n * @returns The number of additional bytes needed for extended length encoding.\r\n *\r\n * @see RFC 6455 §5.2 - Base Framing Protocol\r\n */\r\nexport function calculatePayloadEncodingLength(payloadLength: number): number {\r\n if (payloadLength < PAYLOAD_EXTENDED_16) {\r\n return 0;\r\n }\r\n if (payloadLength < MAX_16BIT_PAYLOAD_LENGTH) {\r\n return EXTENDED_LENGTH_16_BYTES;\r\n }\r\n return EXTENDED_LENGTH_64_BYTES;\r\n}\r\n","import {\n PAYLOAD_EXTENDED_16,\n MAX_16BIT_PAYLOAD_LENGTH,\n EXTENDED_LENGTH_16_BYTES,\n EXTENDED_LENGTH_64_BYTES,\n} from '@/types/constants';\n\n/**\n * Creates a buffer containing the extended payload length for a WebSocket frame.\n *\n * According to RFC 6455 §5.2, payload lengths are encoded as:\n * - 7-bit value (0–125): directly encoded in the header, no extended length bytes needed.\n * - 126 (16-bit): followed by a 2-byte unsigned integer for payload length.\n * - 127 (64-bit): followed by an 8-byte unsigned integer for payload length.\n *\n * This function returns the appropriate extended length field as a buffer.\n * If no extended bytes are needed (i.e. payload < 126), it returns an empty buffer.\n *\n * @param length - The actual payload length.\n * @returns A Buffer containing the encoded extended length, or empty if not needed.\n *\n * @see RFC 6455 §5.2 - Base Framing Protocol\n */\nexport function createPayloadBuffer(length: number): Buffer {\n if (length < PAYLOAD_EXTENDED_16) {\n // Fits in 7-bit field, no extended bytes needed\n return Buffer.alloc(0);\n }\n\n if (length < MAX_16BIT_PAYLOAD_LENGTH) {\n const buf = Buffer.alloc(EXTENDED_LENGTH_16_BYTES);\n buf.writeUInt16BE(length, 0);\n return buf;\n }\n\n const buf = Buffer.alloc(EXTENDED_LENGTH_64_BYTES);\n buf.writeBigUInt64BE(BigInt(length), 0);\n return buf;\n}\n","import {\n OPCODE_MASK,\n PAYLOAD_EXTENDED_16,\n MAX_16BIT_PAYLOAD_LENGTH,\n PAYLOAD_EXTENDED_64,\n} from '@/types/constants';\nimport { PulseFrameHeader } from '../types/pulse-frame-header';\n\n/**\n * Packs the first two bytes of a WebSocket frame header as defined in RFC 6455 §5.2.\n *\n * - `byte0` contains FIN, RSV1–3, and the 4-bit opcode.\n * - `byte1` contains the MASK bit and the payload length indicator.\n *\n * The actual payload length is handled elsewhere. This function determines whether\n * the length fits in 7 bits or requires extended length encoding (16-bit or 64-bit).\n *\n * @param header - The parsed frame header structure.\n * @param payloadLength - The length of the frame’s payload data.\n * @returns A 2-element tuple containing `byte0` and `byte1` to be written to the frame buffer.\n *\n * @see RFC 6455 §5.2 - Base Framing Protocol\n */\nexport const packHeaderBytes = (\n header: PulseFrameHeader,\n payloadLength: number,\n): [number, number] => {\n const byte0 =\n (header.fin << 7) |\n (header.rsv1 << 6) |\n (header.rsv2 << 5) |\n (header.rsv3 << 4) |\n (header.opcode & OPCODE_MASK);\n\n const payloadIndicator =\n payloadLength < PAYLOAD_EXTENDED_16\n ? payloadLength\n : payloadLength < MAX_16BIT_PAYLOAD_LENGTH\n ? PAYLOAD_EXTENDED_16\n : PAYLOAD_EXTENDED_64;\n\n const byte1 = (header.mask << 7) | payloadIndicator;\n\n return [byte0, byte1];\n};\n","import { HEADER_BASE_LENGTH, MASKING_KEY_LENGTH } from '@/types/constants';\r\nimport { calculatePayloadEncodingLength } from '../calculate-payload-encoding-length';\r\nimport { createPayloadBuffer } from '../create-payload-buffer';\r\nimport { maskPayload } from '../mask-payload';\r\nimport { packHeaderBytes } from '../pack-header-bytes';\r\nimport { PulseFrame } from '../types/pulse-frame';\r\n\r\n/**\r\n * Converts a `PulseFrame` object into a raw WebSocket frame buffer.\r\n *\r\n * This function serializes a fully constructed frame into a binary format\r\n * suitable for transmission over a socket, following the WebSocket framing\r\n * rules defined in [RFC 6455 §5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.2).\r\n *\r\n * It handles:\r\n * - Base header byte construction (FIN, RSV1–3, opcode, MASK).\r\n * - Extended payload length encoding if necessary (16-bit or 64-bit).\r\n * - Optional payload masking (XOR with masking key).\r\n * - Concatenation of header and final payload buffer.\r\n *\r\n * @param frame - The `PulseFrame` instance to serialize.\r\n * @returns A `Buffer` containing the fully encoded WebSocket frame.\r\n *\r\n * @see RFC 6455 §5.2 - Base Framing Protocol\r\n */\r\nexport function frameToBuffer(frame: PulseFrame): Buffer {\r\n const payload = frame.getPayload();\r\n const payloadLength = payload.length;\r\n\r\n const extendedLengthBytes = calculatePayloadEncodingLength(payloadLength);\r\n const extendedPayloadBuffer = createPayloadBuffer(payloadLength);\r\n\r\n let headerLength = HEADER_BASE_LENGTH + extendedLengthBytes;\r\n if (frame.isMasked()) {\r\n headerLength += MASKING_KEY_LENGTH;\r\n }\r\n\r\n const finalPayload = frame.isMasked()\r\n ? maskPayload(payload, frame.getHeader().maskingKey!)\r\n : payload;\r\n\r\n const header = Buffer.alloc(headerLength);\r\n const [byte0, byte1] = packHeaderBytes(frame.getHeader(), payloadLength);\r\n header[0] = byte0;\r\n header[1] = byte1;\r\n\r\n extendedPayloadBuffer.copy(header, HEADER_BASE_LENGTH);\r\n\r\n if (frame.isMasked()) {\r\n frame\r\n .getHeader()\r\n .maskingKey!.copy(header, HEADER_BASE_LENGTH + extendedLengthBytes);\r\n }\r\n\r\n return Buffer.concat([header, finalPayload]);\r\n}\r\n"],"mappings":"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","names":["maskPayload","payload","maskingKey","masked","i","PulseFrameOpcode","PulseFrame","header","payloadData","extensionData","applicationData","data","PulseFrameOpcode","createFrame","options","header","payloadData","isMasked","extensionData","payload","maskPayload","PulseFrame","PulseFrameError","_PulseFrameError","message","extractApplicationData","buffer","extensionDataLength","PulseFrameError","extractMaskingKey","buffer","start","length","maskingKeyEnd","PulseFrameError","extractPayload","buffer","start","length","payloadEnd","PulseFrameError","parseExtendedLengthOffset","payloadLen","PulseFrameError","parseHeader","buffer","extensionDataLength","firstByte","secondByte","unmaskPayload","payload","maskingKey","unmasked","i","StrictUtf8Decoder","decodeUtf8Strict","buffer","validateCloseFramePayload","payload","PulseFrameError","code","reasonBuffer","decodeUtf8Strict","validateExtensions","header","extensionSupport","validateOpcode","header","opcode","fin","PulseFrameOpcode","PulseFrameError","isControlFrame","readPayloadLength","buffer","payloadLen","offset","PulseFrameError","extractExtensionData","buffer","start","length","end","PulseFrameError","createFrameFromBuffer","buffer","options","PulseFrameError","extensionDataLength","extensionSupport","frameHeader","parseHeader","isMasked","validateExtensions","validateOpcode","payloadLength","readPayloadLength","extendedLengthOffset","parseExtendedLengthOffset","maskingKey","extractMaskingKey","readOffset","rawPayloadData","extractPayload","payloadData","unmaskPayload","extensionData","extractExtensionData","applicationData","extractApplicationData","validateCloseFramePayload","PulseFrame","createFrameFromControlCode","payload","opcode","data","PulseFrameError","header","PulseFrame","createFrameFromText","text","payload","header","PulseFrame","calculatePayloadEncodingLength","payloadLength","createPayloadBuffer","length","buf","packHeaderBytes","header","payloadLength","byte0","payloadIndicator","byte1","frameToBuffer","frame","payload","payloadLength","extendedLengthBytes","calculatePayloadEncodingLength","extendedPayloadBuffer","createPayloadBuffer","headerLength","finalPayload","maskPayload","header","byte0","byte1","packHeaderBytes"]}