import { mkdtempSync, rmSync } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import native, { type NativeAddon, type ParserOptions } from '../src/native'; const { existsSyncMock } = vi.hoisted(() => ({ existsSyncMock: vi.fn<(target: string) => boolean>(), })); const fsHooks = vi.hoisted(() => ({ realExistsSync: (_target: string): boolean => false, })); vi.mock('node:fs', async (importOriginal) => { const actual = await importOriginal(); fsHooks.realExistsSync = (target) => actual.existsSync(target); existsSyncMock.mockImplementation((target) => actual.existsSync(target)); return { ...actual, existsSync: (target: Parameters[0]) => existsSyncMock(String(target)), }; }); const ORIGINAL_DEV_NO_NATIVE = process.env.SNOWLUMA_DEV_NO_NATIVE; const ORIGINAL_CWD = process.cwd(); const NATIVE_SRC_DIR = path.dirname(fileURLToPath(new URL('../src/native.ts', import.meta.url))); const BINARY_NAME = `websocket-${process.platform}-${process.arch}.node`; const tmpDirs: string[] = []; const RFC_KEY = 'dGhlIHNhbXBsZSBub25jZQ=='; const RFC_ACCEPT = 's3pPLMBiTxaQ9kYGzzhZRbK+xOo='; const RFC_UNMASKED_HELLO = Buffer.from([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]); const RFC_MASKED_HELLO = Buffer.from([ 0x81, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58, ]); const RFC_MASK = Buffer.from([0x37, 0xfa, 0x21, 0x3d]); const HELLO = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); const STUB_USE_SUFFIX = 'Set SNOWLUMA_DEV_NO_NATIVE=0 (or unset) and provide the prebuilt binary to enable WebSocket I/O.'; let nativeIo = false; try { nativeIo = native.computeAcceptKey(RFC_KEY) === RFC_ACCEPT; } catch (err) { nativeIo = !String((err as Error).message).includes('native addon is stubbed'); } function expectedSearchDirs(cwd = process.cwd()): string[] { return [ path.resolve(NATIVE_SRC_DIR, 'native'), path.resolve(NATIVE_SRC_DIR, '..', 'native'), path.resolve(NATIVE_SRC_DIR, '..', '..', 'runtime', 'native'), path.resolve(cwd, 'native'), path.resolve(cwd, 'dist', 'native'), path.resolve(cwd, 'packages', 'runtime', 'native'), ]; } function candidatePaths(cwd = process.cwd()): string[] { return expectedSearchDirs(cwd).map((dir) => path.join(dir, BINARY_NAME)); } function notFoundMessage(cwd = process.cwd()): string { return `[snowluma/websocket] native addon not found: ${BINARY_NAME}. Searched: ${expectedSearchDirs(cwd).join(', ')}`; } function stubWarnMessage(reason: string): string { return `[snowluma/websocket] WARNING: using stub addon (${reason}). WebSocket features are disabled.`; } function stubUseMessage(reason: string): string { return `[snowluma/websocket] native addon is stubbed (${reason}). ${STUB_USE_SUFFIX}`; } function restoreExistsSync(): void { existsSyncMock.mockReset(); existsSyncMock.mockImplementation((target) => fsHooks.realExistsSync(target)); } function restoreDevNoNative(): void { if (ORIGINAL_DEV_NO_NATIVE === undefined) { delete process.env.SNOWLUMA_DEV_NO_NATIVE; } else { process.env.SNOWLUMA_DEV_NO_NATIVE = ORIGINAL_DEV_NO_NATIVE; } } async function loadNative(): Promise { const addon = (await import('../src/native')).default; void addon.Parser; return addon; } function installFakeAddon(exports: NativeAddon): string[] { const opened: string[] = []; vi.spyOn(process, 'dlopen').mockImplementation((module, filename) => { opened.push(filename); module.exports = exports; }); return opened; } function fakeAddon(acceptKey: string): NativeAddon { return { Parser: class { push() { return { frames: [] }; } } as unknown as NativeAddon['Parser'], buildFrame: () => Buffer.from([0x81, 0x00]), computeAcceptKey: () => acceptKey, }; } function makeTmpDir(): string { const dir = mkdtempSync(path.join(os.tmpdir(), 'sl-ws-native-')); tmpDirs.push(dir); return dir; } function cleanupLoaderTest(): void { process.chdir(ORIGINAL_CWD); restoreDevNoNative(); restoreExistsSync(); vi.restoreAllMocks(); vi.resetModules(); for (const dir of tmpDirs.splice(0)) { rmSync(dir, { recursive: true, force: true }); } } describe.skipIf(!nativeIo)('computeAcceptKey', () => { it('returns the RFC 6455 §1.3 Sec-WebSocket-Accept value', () => { expect(native.computeAcceptKey(RFC_KEY)).toBe(RFC_ACCEPT); }); it('rejects a non-string key', () => { expect(() => native.computeAcceptKey(1 as unknown as string)).toThrow('expected string'); }); }); describe.skipIf(!nativeIo)('buildFrame', () => { it('emits the RFC 6455 §5.7 unmasked Hello text frame', () => { expect(native.buildFrame(0x1, true, HELLO, null, 0)).toEqual(RFC_UNMASKED_HELLO); }); it('emits the RFC 6455 §5.7 masked Hello text frame', () => { expect(native.buildFrame(0x1, true, HELLO, RFC_MASK, 0)).toEqual(RFC_MASKED_HELLO); }); it('encodes a 16-bit length for a 126-byte payload', () => { const payload = Buffer.alloc(126, 0x55); expect(native.buildFrame(0x2, true, payload, null, 0)).toEqual( Buffer.concat([Buffer.from([0x82, 0x7e, 0x00, 0x7e]), payload]), ); }); it('encodes a 64-bit length for a 65536-byte payload', () => { const payload = Buffer.alloc(65536, 0x62); expect(native.buildFrame(0x2, true, payload, null, 0)).toEqual( Buffer.concat([ Buffer.from([0x82, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00]), payload, ]), ); }); it('masks a 16-bit-length payload with the 4-byte key', () => { const payload = Buffer.alloc(126, 0x55); const mask = Buffer.from([0x01, 0x02, 0x03, 0x04]); const body = Buffer.from(payload); for (let i = 0; i < body.length; i++) { body[i]! ^= mask[i & 3]!; } expect(native.buildFrame(0x2, true, payload, mask, 0)).toEqual( Buffer.concat([Buffer.from([0x82, 0xfe, 0x00, 0x7e, 0x01, 0x02, 0x03, 0x04]), body]), ); }); it('writes FIN, RSV, and opcode into the first header byte', () => { expect(native.buildFrame(0x1, false, Buffer.from([0x48, 0x65, 0x6c]), null, 0)).toEqual( Buffer.from([0x01, 0x03, 0x48, 0x65, 0x6c]), ); expect(native.buildFrame(0x1, true, Buffer.alloc(0), null, 0x40)).toEqual( Buffer.from([0xc1, 0x00]), ); expect(native.buildFrame(0x11, true, Buffer.alloc(0), null, 0x41)).toEqual( Buffer.from([0xc1, 0x00]), ); expect(native.buildFrame(0x8, true, Buffer.from([0x03, 0xe8]), null, 0)).toEqual( Buffer.from([0x88, 0x02, 0x03, 0xe8]), ); expect(native.buildFrame(0x9, true, Buffer.alloc(0), null, 0)).toEqual( Buffer.from([0x89, 0x00]), ); }); it('rejects a mask key that is not 4 bytes', () => { expect(() => native.buildFrame(0x1, true, HELLO, Buffer.from([0x00, 0x01, 0x02]), 0), ).toThrow('mask key must be 4 bytes'); }); it('rejects a call that is missing the required arguments', () => { expect(() => (native.buildFrame as unknown as (opcode: number) => Buffer)(0x1)).toThrow( 'bad args', ); }); }); describe.skipIf(!nativeIo)('Parser', () => { it('parses the RFC 6455 unmasked Hello frame as a client', () => { const parser = new native.Parser({ isServer: false, maxPayload: 1024, allowedRsv: 0 }); expect(parser.push(RFC_UNMASKED_HELLO)).toEqual({ frames: [{ fin: true, rsv: 0, opcode: 0x1, payload: HELLO }], }); }); it('unmasks the RFC 6455 masked Hello frame as a server', () => { const parser = new native.Parser({ isServer: true, maxPayload: 1024, allowedRsv: 0 }); expect(parser.push(RFC_MASKED_HELLO)).toEqual({ frames: [{ fin: true, rsv: 0, opcode: 0x1, payload: HELLO }], }); }); it('returns no frames until a split header and payload are complete', () => { const parser = new native.Parser({ isServer: false, maxPayload: 1024, allowedRsv: 0 }); expect(parser.push(Buffer.from([0x81]))).toEqual({ frames: [] }); expect(parser.push(Buffer.from([0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]))).toEqual({ frames: [{ fin: true, rsv: 0, opcode: 0x1, payload: HELLO }], }); }); it('parses two frames from one chunk', () => { const parser = new native.Parser({ isServer: false, maxPayload: 1024, allowedRsv: 0 }); expect(parser.push(Buffer.concat([RFC_UNMASKED_HELLO, Buffer.from([0x89, 0x00])]))).toEqual( { frames: [ { fin: true, rsv: 0, opcode: 0x1, payload: HELLO }, { fin: true, rsv: 0, opcode: 0x9, payload: Buffer.alloc(0) }, ], }, ); }); it('parses 16-bit and 64-bit unmasked payloads', () => { const mid = Buffer.alloc(126, 0x41); const large = Buffer.alloc(65536, 0x42); const midParser = new native.Parser({ isServer: false, maxPayload: 200, allowedRsv: 0 }); expect(midParser.push(Buffer.concat([Buffer.from([0x82, 0x7e, 0x00, 0x7e]), mid]))).toEqual( { frames: [{ fin: true, rsv: 0, opcode: 0x2, payload: mid }], }, ); const largeParser = new native.Parser({ isServer: false, maxPayload: 70_000, allowedRsv: 0, }); expect( largeParser.push( Buffer.concat([ Buffer.from([0x82, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00]), large, ]), ), ).toEqual({ frames: [{ fin: true, rsv: 0, opcode: 0x2, payload: large }], }); }); it('exposes allowed RSV1 and defaults to a masking server', () => { const deflate = new native.Parser({ isServer: false, maxPayload: 1024, allowedRsv: 0x40 }); expect(deflate.push(Buffer.from([0xc1, 0x00]))).toEqual({ frames: [{ fin: true, rsv: 0x40, opcode: 0x1, payload: Buffer.alloc(0) }], }); const defaults = new native.Parser({} as ParserOptions); expect(defaults.push(RFC_UNMASKED_HELLO)).toEqual({ error: true, code: 1002, message: 'Expected masked frame from client', }); }); it('reports protocol errors with the native close code and message', () => { const client: ParserOptions = { isServer: false, maxPayload: 1, allowedRsv: 0 }; expect(new native.Parser(client).push(Buffer.from([0xc1, 0x00]))).toEqual({ error: true, code: 1002, message: 'Unexpected RSV bits', }); expect(new native.Parser(client).push(Buffer.from([0x83, 0x00]))).toEqual({ error: true, code: 1002, message: 'Unknown opcode', }); expect(new native.Parser(client).push(Buffer.from([0x09, 0x00]))).toEqual({ error: true, code: 1002, message: 'Fragmented control frame', }); expect(new native.Parser(client).push(Buffer.from([0x89, 0x7e, 0x00, 0x7e]))).toEqual({ error: true, code: 1002, message: 'Control frame too large', }); expect(new native.Parser(client).push(Buffer.from([0x81, 0x80]))).toEqual({ error: true, code: 1002, message: 'Server must not mask frames', }); expect(new native.Parser(client).push(Buffer.from([0x82, 0x7e, 0x00, 0x05]))).toEqual({ error: true, code: 1002, message: 'Non-minimal 16-bit length', }); expect( new native.Parser(client).push( Buffer.from([0x82, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05]), ), ).toEqual({ error: true, code: 1002, message: 'Non-minimal 64-bit length', }); expect( new native.Parser(client).push( Buffer.from([0x82, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), ), ).toEqual({ error: true, code: 1002, message: 'Payload length top bit set', }); expect(new native.Parser(client).push(Buffer.from([0x82, 0x02]))).toEqual({ error: true, code: 1009, message: 'Payload exceeds configured max', }); expect( new native.Parser({ isServer: true, maxPayload: 1024, allowedRsv: 0 }).push( Buffer.from([0x81, 0x00]), ), ).toEqual({ error: true, code: 1002, message: 'Expected masked frame from client', }); expect( new native.Parser({ isServer: false, maxPayload: 1024, allowedRsv: 0x40 }).push( Buffer.from([0xc9, 0x00]), ), ).toEqual({ error: true, code: 1002, message: 'Control frame RSV bits must be zero', }); }); it('treats a negative maxPayload as zero and masks allowedRsv to RSV bits', () => { const zeroCap = new native.Parser({ isServer: false, maxPayload: -1, allowedRsv: 0 }); expect(zeroCap.push(Buffer.from([0x81, 0x00]))).toEqual({ frames: [{ fin: true, rsv: 0, opcode: 0x1, payload: Buffer.alloc(0) }], }); expect(zeroCap.push(Buffer.from([0x81, 0x01, 0x61]))).toEqual({ error: true, code: 1009, message: 'Payload exceeds configured max', }); const rsvMasked = new native.Parser({ isServer: false, maxPayload: 1024, allowedRsv: 0x41, }); expect(rsvMasked.push(Buffer.from([0xc1, 0x00]))).toEqual({ frames: [{ fin: true, rsv: 0x40, opcode: 0x1, payload: Buffer.alloc(0) }], }); }); it('rejects a non-Buffer push argument', () => { const parser = new native.Parser({ isServer: false, maxPayload: 1024, allowedRsv: 0 }); expect(() => parser.push('hello' as unknown as Buffer)).toThrow('expected Buffer'); }); }); describe('addon path resolution', () => { beforeEach(() => { delete process.env.SNOWLUMA_DEV_NO_NATIVE; restoreExistsSync(); vi.resetModules(); }); afterEach(cleanupLoaderTest); it('does not load a native library until first use, then caches it', async () => { const opened = installFakeAddon(fakeAddon('lazy')); existsSyncMock.mockReturnValue(true); const addon = (await import('../src/native')).default; expect(opened).toEqual([]); expect(existsSyncMock).not.toHaveBeenCalled(); expect(addon.computeAcceptKey(RFC_KEY)).toBe('lazy'); expect(addon.computeAcceptKey(RFC_KEY)).toBe('lazy'); expect(opened).toHaveLength(1); }); it('dlopens the first existing websocket--.node candidate', async () => { const opened = installFakeAddon(fakeAddon('from-first-hit')); const winner = candidatePaths()[2]!; const probed: string[] = []; existsSyncMock.mockImplementation((target) => { probed.push(target); return target === winner; }); const addon = await loadNative(); expect(probed).toEqual(candidatePaths().slice(0, 3)); expect(opened).toEqual([winner]); expect(addon.computeAcceptKey(RFC_KEY)).toBe('from-first-hit'); }); it('prefers the flattened /native layout over later candidates', async () => { const opened = installFakeAddon(fakeAddon('flattened')); const winner = candidatePaths()[0]!; existsSyncMock.mockImplementation((target) => target === winner); await loadNative(); expect(opened).toEqual([winner]); expect(existsSyncMock.mock.calls.map(([target]) => target)).toEqual([winner]); }); it('falls back to process.cwd()/native when module-relative binaries are absent', async () => { process.chdir(makeTmpDir()); // Node may report /private/var/folders/... after chdir while mkdtemp // returned /var/folders/...; searchDirs() uses process.cwd(). const cwd = process.cwd(); const opened = installFakeAddon(fakeAddon('cwd-native')); const winner = path.join(cwd, 'native', BINARY_NAME); existsSyncMock.mockImplementation((target) => target === winner); const addon = await loadNative(); expect(opened).toEqual([winner]); expect(addon.computeAcceptKey(RFC_KEY)).toBe('cwd-native'); }); it('throws the searched-path error when no candidate exists', async () => { process.chdir(makeTmpDir()); const cwd = process.cwd(); const probed: string[] = []; existsSyncMock.mockImplementation((target) => { probed.push(target); return false; }); let thrown: unknown; try { await loadNative(); } catch (err) { thrown = err; } expect(probed).toEqual(candidatePaths(cwd)); expect(thrown).toBeInstanceOf(Error); expect((thrown as Error).message).toBe(notFoundMessage(cwd)); }); it('does not stub a successful load when SNOWLUMA_DEV_NO_NATIVE=1', async () => { process.env.SNOWLUMA_DEV_NO_NATIVE = '1'; const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); existsSyncMock.mockImplementation((target) => target === candidatePaths()[0]); installFakeAddon(fakeAddon('still-live')); const addon = await loadNative(); expect(warn).not.toHaveBeenCalled(); expect(addon.computeAcceptKey(RFC_KEY)).toBe('still-live'); expect(addon.buildFrame(0x1, true, Buffer.alloc(0), null, 0)).toEqual( Buffer.from([0x81, 0x00]), ); }); }); describe('SNOWLUMA_DEV_NO_NATIVE stub', () => { beforeEach(() => { delete process.env.SNOWLUMA_DEV_NO_NATIVE; restoreExistsSync(); vi.resetModules(); }); afterEach(cleanupLoaderTest); it('stubs after a missing binary when the env var is exactly 1', async () => { process.env.SNOWLUMA_DEV_NO_NATIVE = '1'; existsSyncMock.mockReturnValue(false); const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); const addon = await loadNative(); const reason = notFoundMessage(); expect(warn).toHaveBeenCalledTimes(1); expect(warn).toHaveBeenCalledWith(stubWarnMessage(reason)); const parser = new addon.Parser({ isServer: true, maxPayload: 1, allowedRsv: 0 }); expect(() => parser.push(Buffer.alloc(0))).toThrow(stubUseMessage(reason)); expect(() => addon.buildFrame(0x1, true, Buffer.alloc(0), null, 0)).toThrow( stubUseMessage(reason), ); expect(() => addon.computeAcceptKey(RFC_KEY)).toThrow(stubUseMessage(reason)); }); it('stubs after process.dlopen fails when the env var is exactly 1', async () => { process.env.SNOWLUMA_DEV_NO_NATIVE = '1'; existsSyncMock.mockReturnValue(true); vi.spyOn(process, 'dlopen').mockImplementation(() => { throw new Error('not a valid Node addon'); }); const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); const addon = await loadNative(); expect(warn).toHaveBeenCalledWith(stubWarnMessage('not a valid Node addon')); expect(() => addon.computeAcceptKey(RFC_KEY)).toThrow( stubUseMessage('not a valid Node addon'), ); }); it('rethrows a missing-binary error when the env var is not 1', async () => { existsSyncMock.mockReturnValue(false); for (const value of [undefined, '', '0', 'true', '2'] as const) { vi.resetModules(); if (value === undefined) { delete process.env.SNOWLUMA_DEV_NO_NATIVE; } else { process.env.SNOWLUMA_DEV_NO_NATIVE = value; } let thrown: unknown; try { await loadNative(); } catch (err) { thrown = err; } expect(thrown).toBeInstanceOf(Error); expect((thrown as Error).message).toBe(notFoundMessage()); } }); it('rethrows a dlopen failure when the env var is unset', async () => { existsSyncMock.mockReturnValue(true); vi.spyOn(process, 'dlopen').mockImplementation(() => { throw new Error('not a valid Node addon'); }); await expect(loadNative()).rejects.toThrow('not a valid Node addon'); }); });