quickwin 2026.5.2-3.145209

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +6 -0
  2. package/examples/pdf_preview.js +440 -0
  3. package/examples/pdf_preview.ts +470 -0
  4. package/examples/preact_demo.js +35 -0
  5. package/examples/preact_demo.tsx +49 -0
  6. package/examples/tray_demo.js +75 -0
  7. package/examples/tray_demo.tsx +79 -0
  8. package/lib/fetch.js +746 -0
  9. package/lib/fetch.ts +811 -0
  10. package/lib/polyfill.js +500 -0
  11. package/lib/polyfill.ts +454 -0
  12. package/lib/preact/hooks.js +287 -0
  13. package/lib/preact/hooks.ts +330 -0
  14. package/lib/preact/jsx-runtime.js +1 -0
  15. package/lib/preact/jsx-runtime.ts +2 -0
  16. package/lib/preact/jsx.d.ts +36 -0
  17. package/lib/preact/layout.js +153 -0
  18. package/lib/preact/layout.ts +183 -0
  19. package/lib/preact/preact.js +54 -0
  20. package/lib/preact/preact.ts +133 -0
  21. package/lib/preact/props.js +99 -0
  22. package/lib/preact/props.ts +119 -0
  23. package/lib/preact/render.js +320 -0
  24. package/lib/preact/render.ts +353 -0
  25. package/lib/websocket.js +540 -0
  26. package/lib/websocket.ts +574 -0
  27. package/package.json +32 -0
  28. package/quickwin.d.ts +657 -0
  29. package/test/add.wasm +0 -0
  30. package/test/complex.wasm +0 -0
  31. package/test/complex_imports.wasm +0 -0
  32. package/test/global_imports.wasm +0 -0
  33. package/test/import_func.wasm +0 -0
  34. package/test/imports.wasm +0 -0
  35. package/test/run.js +86 -0
  36. package/test/run.ts +90 -0
  37. package/test/sjlj.wasm +0 -0
  38. package/test/test_basic.js +7 -0
  39. package/test/test_basic.ts +9 -0
  40. package/test/test_brotli.js +48 -0
  41. package/test/test_brotli.ts +52 -0
  42. package/test/test_fetch_cache.js +131 -0
  43. package/test/test_fetch_cache.ts +141 -0
  44. package/test/test_ffi.js +157 -0
  45. package/test/test_ffi.ts +174 -0
  46. package/test/test_frame_encoding.js +128 -0
  47. package/test/test_frame_encoding.ts +132 -0
  48. package/test/test_helper.js +84 -0
  49. package/test/test_helper.ts +80 -0
  50. package/test/test_http_import.js +78 -0
  51. package/test/test_http_import.ts +74 -0
  52. package/test/test_mupdf_render.js +69 -0
  53. package/test/test_mupdf_render.ts +74 -0
  54. package/test/test_mupdf_twice.js +77 -0
  55. package/test/test_mupdf_twice.ts +81 -0
  56. package/test/test_mupdf_wasm.js +33 -0
  57. package/test/test_mupdf_wasm.ts +30 -0
  58. package/test/test_net_event.js +63 -0
  59. package/test/test_net_event.ts +59 -0
  60. package/test/test_net_fetch.js +153 -0
  61. package/test/test_net_fetch.ts +131 -0
  62. package/test/test_net_websocket.js +158 -0
  63. package/test/test_net_websocket.ts +144 -0
  64. package/test/test_polyfill.js +58 -0
  65. package/test/test_polyfill.ts +60 -0
  66. package/test/test_url.js +173 -0
  67. package/test/test_url.ts +183 -0
  68. package/test/test_wasm_basic.js +82 -0
  69. package/test/test_wasm_basic.ts +70 -0
  70. package/test/test_wasm_import_global.js +41 -0
  71. package/test/test_wasm_import_global.ts +39 -0
  72. package/test/test_wasm_sjlj.js +153 -0
  73. package/test/test_wasm_sjlj.ts +134 -0
  74. package/test/test_wasm_types.js +96 -0
  75. package/test/test_wasm_types.ts +108 -0
  76. package/test/types.wasm +0 -0
  77. package/tsconfig.json +18 -0
  78. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
  79. package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
  80. package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
  81. package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
  82. package/vendor/mupdf-wasm/mupdf.js +3317 -0
  83. package/win-mingw64.exe +0 -0
@@ -0,0 +1,157 @@
1
+ import * as std from 'std';
2
+ import * as win from 'win';
3
+ import * as ffi from 'ffi';
4
+ function decodeWideAtPtr(ptr) {
5
+ if (!ptr)
6
+ return '';
7
+ const chars = [];
8
+ let pos = ptr;
9
+ while (true) {
10
+ const low = ffi.readByte(pos);
11
+ const high = ffi.readByte(pos + 1);
12
+ const ch = low + high * 256;
13
+ if (ch === 0)
14
+ break;
15
+ chars.push(ch);
16
+ pos += 2;
17
+ }
18
+ return String.fromCharCode(...chars);
19
+ }
20
+ function readPtr(dv, offset) {
21
+ const low = dv.getUint32(offset, true);
22
+ const high = dv.getUint32(offset + 4, true);
23
+ return low + high * 4294967296;
24
+ }
25
+ const STATUS_FLAGS = [
26
+ [0x00000001, 'PAUSED'],
27
+ [0x00000002, 'ERROR'],
28
+ [0x00000004, 'PENDING_DELETION'],
29
+ [0x00000008, 'PAPER_JAM'],
30
+ [0x00000010, 'PAPER_OUT'],
31
+ [0x00000020, 'MANUAL_FEED'],
32
+ [0x00000040, 'PAPER_PROBLEM'],
33
+ [0x00000080, 'OFFLINE'],
34
+ [0x00000100, 'IO_ACTIVE'],
35
+ [0x00000200, 'BUSY'],
36
+ [0x00000400, 'PRINTING'],
37
+ [0x00000800, 'OUTPUT_BIN_FULL'],
38
+ [0x00001000, 'NOT_AVAILABLE'],
39
+ [0x00002000, 'WAITING'],
40
+ [0x00004000, 'PROCESSING'],
41
+ [0x00008000, 'INITIALIZING'],
42
+ [0x00010000, 'WARMING_UP'],
43
+ [0x00020000, 'TONER_LOW'],
44
+ [0x00040000, 'NO_TONER'],
45
+ [0x00080000, 'PAGE_PUNT'],
46
+ [0x00100000, 'USER_INTERVENTION'],
47
+ [0x00200000, 'OUT_OF_MEMORY'],
48
+ [0x00400000, 'DOOR_OPEN'],
49
+ [0x00800000, 'SERVER_UNKNOWN'],
50
+ [0x01000000, 'POWER_SAVE'],
51
+ [0x02000000, 'SERVER_OFFLINE'],
52
+ [0x04000000, 'DRIVER_UPDATE'],
53
+ ];
54
+ function formatStatus(status) {
55
+ if (status === 0)
56
+ return 'OK';
57
+ const parts = [];
58
+ for (const [flag, name] of STATUS_FLAGS) {
59
+ if (status & flag)
60
+ parts.push(name);
61
+ }
62
+ return parts.join('|') || 'UNKNOWN';
63
+ }
64
+ export const suite = {
65
+ name: 'ffi',
66
+ run: (t) => {
67
+ t.section('EnumPrintersW');
68
+ const hWinspool = win.LoadLibrary('winspool.drv');
69
+ t.checkTrue('LoadLibrary("winspool.drv") succeeds', hWinspool !== null);
70
+ if (!hWinspool)
71
+ return;
72
+ const enumPrinters = win.GetProcAddress(hWinspool, 'EnumPrintersW');
73
+ t.checkTrue('GetProcAddress("EnumPrintersW") succeeds', enumPrinters !== null);
74
+ if (!enumPrinters)
75
+ return;
76
+ const flags = 0x06;
77
+ const level = 2;
78
+ const neededBuf = new Uint32Array(new ArrayBuffer(4));
79
+ const returnedBuf = new Uint32Array(new ArrayBuffer(4));
80
+ const ret1 = ffi.ffiCall(enumPrinters, [
81
+ ffi.FFI_TYPE_UINT32,
82
+ ffi.FFI_TYPE_POINTER,
83
+ ffi.FFI_TYPE_UINT32,
84
+ ffi.FFI_TYPE_POINTER,
85
+ ffi.FFI_TYPE_UINT32,
86
+ ffi.FFI_TYPE_POINTER,
87
+ ffi.FFI_TYPE_POINTER,
88
+ ], [
89
+ flags,
90
+ null,
91
+ level,
92
+ null,
93
+ 0,
94
+ neededBuf.buffer,
95
+ returnedBuf.buffer,
96
+ ], ffi.FFI_TYPE_SINT32);
97
+ std.printf(' first call: ret=%d needed=%d returned=%d\n', ret1, neededBuf[0], returnedBuf[0]);
98
+ t.checkTrue('pcbNeeded > 0', neededBuf[0] > 0);
99
+ if (neededBuf[0] <= 0)
100
+ return;
101
+ const printerBuf = new ArrayBuffer(neededBuf[0]);
102
+ const ret2 = ffi.ffiCall(enumPrinters, [
103
+ ffi.FFI_TYPE_UINT32,
104
+ ffi.FFI_TYPE_POINTER,
105
+ ffi.FFI_TYPE_UINT32,
106
+ ffi.FFI_TYPE_POINTER,
107
+ ffi.FFI_TYPE_UINT32,
108
+ ffi.FFI_TYPE_POINTER,
109
+ ffi.FFI_TYPE_POINTER,
110
+ ], [
111
+ flags,
112
+ null,
113
+ level,
114
+ printerBuf,
115
+ neededBuf[0],
116
+ neededBuf.buffer,
117
+ returnedBuf.buffer,
118
+ ], ffi.FFI_TYPE_SINT32);
119
+ t.checkTrue('EnumPrintersW succeeds', ret2 !== 0);
120
+ if (ret2 === 0)
121
+ return;
122
+ std.printf(' printers found: %d\n', returnedBuf[0]);
123
+ if (returnedBuf[0] > 0) {
124
+ const dv = new DataView(printerBuf);
125
+ const structSize = 136;
126
+ for (let i = 0; i < returnedBuf[0] && i < 20; i++) {
127
+ const off = i * structSize;
128
+ const name = decodeWideAtPtr(readPtr(dv, off + 8));
129
+ const port = decodeWideAtPtr(readPtr(dv, off + 24));
130
+ const driver = decodeWideAtPtr(readPtr(dv, off + 32));
131
+ const location = decodeWideAtPtr(readPtr(dv, off + 48));
132
+ const comment = decodeWideAtPtr(readPtr(dv, off + 40));
133
+ const status = dv.getUint32(off + 124, true);
134
+ std.printf(' [%d] %s\n', i + 1, name);
135
+ std.printf(' port: %s\n', port);
136
+ std.printf(' driver: %s\n', driver);
137
+ if (location)
138
+ std.printf(' location: %s\n', location);
139
+ if (comment)
140
+ std.printf(' comment: %s\n', comment);
141
+ std.printf(' status: 0x%08X (%s)\n', status, formatStatus(status));
142
+ }
143
+ }
144
+ else {
145
+ std.printf(' no printers installed (skip)\n');
146
+ }
147
+ t.section('pointer arg type check');
148
+ let threw = false;
149
+ try {
150
+ ffi.ffiCall(enumPrinters, [ffi.FFI_TYPE_POINTER], [123], ffi.FFI_TYPE_VOID);
151
+ }
152
+ catch (e) {
153
+ threw = true;
154
+ }
155
+ t.checkTrue('non-ArrayBuffer pointer arg throws TypeError', threw);
156
+ }
157
+ };
@@ -0,0 +1,174 @@
1
+ import * as std from 'std'
2
+ import * as win from 'win'
3
+ import * as ffi from 'ffi'
4
+ import { Tester } from './test_helper.js'
5
+
6
+ function decodeWideAtPtr(ptr: number): string {
7
+ if (!ptr) return ''
8
+ const chars: number[] = []
9
+ let pos = ptr
10
+ while (true) {
11
+ const low = ffi.readByte(pos) as number
12
+ const high = ffi.readByte(pos + 1) as number
13
+ const ch = low + high * 256
14
+ if (ch === 0) break
15
+ chars.push(ch)
16
+ pos += 2
17
+ }
18
+ return String.fromCharCode(...chars)
19
+ }
20
+
21
+ function readPtr(dv: DataView, offset: number): number {
22
+ const low = dv.getUint32(offset, true)
23
+ const high = dv.getUint32(offset + 4, true)
24
+ return low + high * 4294967296
25
+ }
26
+
27
+ const STATUS_FLAGS: [number, string][] = [
28
+ [0x00000001, 'PAUSED'],
29
+ [0x00000002, 'ERROR'],
30
+ [0x00000004, 'PENDING_DELETION'],
31
+ [0x00000008, 'PAPER_JAM'],
32
+ [0x00000010, 'PAPER_OUT'],
33
+ [0x00000020, 'MANUAL_FEED'],
34
+ [0x00000040, 'PAPER_PROBLEM'],
35
+ [0x00000080, 'OFFLINE'],
36
+ [0x00000100, 'IO_ACTIVE'],
37
+ [0x00000200, 'BUSY'],
38
+ [0x00000400, 'PRINTING'],
39
+ [0x00000800, 'OUTPUT_BIN_FULL'],
40
+ [0x00001000, 'NOT_AVAILABLE'],
41
+ [0x00002000, 'WAITING'],
42
+ [0x00004000, 'PROCESSING'],
43
+ [0x00008000, 'INITIALIZING'],
44
+ [0x00010000, 'WARMING_UP'],
45
+ [0x00020000, 'TONER_LOW'],
46
+ [0x00040000, 'NO_TONER'],
47
+ [0x00080000, 'PAGE_PUNT'],
48
+ [0x00100000, 'USER_INTERVENTION'],
49
+ [0x00200000, 'OUT_OF_MEMORY'],
50
+ [0x00400000, 'DOOR_OPEN'],
51
+ [0x00800000, 'SERVER_UNKNOWN'],
52
+ [0x01000000, 'POWER_SAVE'],
53
+ [0x02000000, 'SERVER_OFFLINE'],
54
+ [0x04000000, 'DRIVER_UPDATE'],
55
+ ];
56
+
57
+ function formatStatus(status: number): string {
58
+ if (status === 0) return 'OK'
59
+ const parts: string[] = []
60
+ for (const [flag, name] of STATUS_FLAGS) {
61
+ if (status & flag) parts.push(name)
62
+ }
63
+ return parts.join('|') || 'UNKNOWN'
64
+ }
65
+
66
+ export const suite = {
67
+ name: 'ffi',
68
+ run: (t: Tester) => {
69
+ t.section('EnumPrintersW')
70
+ const hWinspool = win.LoadLibrary('winspool.drv')
71
+ t.checkTrue('LoadLibrary("winspool.drv") succeeds', hWinspool !== null)
72
+ if (!hWinspool) return
73
+
74
+ const enumPrinters = win.GetProcAddress(hWinspool, 'EnumPrintersW')
75
+ t.checkTrue('GetProcAddress("EnumPrintersW") succeeds', enumPrinters !== null)
76
+ if (!enumPrinters) return
77
+
78
+ const flags = 0x06
79
+ const level = 2
80
+ const neededBuf = new Uint32Array(new ArrayBuffer(4))
81
+ const returnedBuf = new Uint32Array(new ArrayBuffer(4))
82
+
83
+ const ret1 = ffi.ffiCall(
84
+ enumPrinters,
85
+ [
86
+ ffi.FFI_TYPE_UINT32,
87
+ ffi.FFI_TYPE_POINTER,
88
+ ffi.FFI_TYPE_UINT32,
89
+ ffi.FFI_TYPE_POINTER,
90
+ ffi.FFI_TYPE_UINT32,
91
+ ffi.FFI_TYPE_POINTER,
92
+ ffi.FFI_TYPE_POINTER,
93
+ ],
94
+ [
95
+ flags,
96
+ null,
97
+ level,
98
+ null,
99
+ 0,
100
+ neededBuf.buffer,
101
+ returnedBuf.buffer,
102
+ ],
103
+ ffi.FFI_TYPE_SINT32
104
+ )
105
+ std.printf(' first call: ret=%d needed=%d returned=%d\n', ret1, neededBuf[0], returnedBuf[0])
106
+ t.checkTrue('pcbNeeded > 0', neededBuf[0] > 0)
107
+ if (neededBuf[0] <= 0) return
108
+
109
+ const printerBuf = new ArrayBuffer(neededBuf[0])
110
+ const ret2 = ffi.ffiCall(
111
+ enumPrinters,
112
+ [
113
+ ffi.FFI_TYPE_UINT32,
114
+ ffi.FFI_TYPE_POINTER,
115
+ ffi.FFI_TYPE_UINT32,
116
+ ffi.FFI_TYPE_POINTER,
117
+ ffi.FFI_TYPE_UINT32,
118
+ ffi.FFI_TYPE_POINTER,
119
+ ffi.FFI_TYPE_POINTER,
120
+ ],
121
+ [
122
+ flags,
123
+ null,
124
+ level,
125
+ printerBuf,
126
+ neededBuf[0],
127
+ neededBuf.buffer,
128
+ returnedBuf.buffer,
129
+ ],
130
+ ffi.FFI_TYPE_SINT32
131
+ )
132
+ t.checkTrue('EnumPrintersW succeeds', ret2 !== 0)
133
+ if (ret2 === 0) return
134
+
135
+ std.printf(' printers found: %d\n', returnedBuf[0])
136
+
137
+ if (returnedBuf[0] > 0) {
138
+ const dv = new DataView(printerBuf)
139
+ const structSize = 136
140
+
141
+ for (let i = 0; i < returnedBuf[0] && i < 20; i++) {
142
+ const off = i * structSize
143
+ const name = decodeWideAtPtr(readPtr(dv, off + 8))
144
+ const port = decodeWideAtPtr(readPtr(dv, off + 24))
145
+ const driver = decodeWideAtPtr(readPtr(dv, off + 32))
146
+ const location = decodeWideAtPtr(readPtr(dv, off + 48))
147
+ const comment = decodeWideAtPtr(readPtr(dv, off + 40))
148
+ const status = dv.getUint32(off + 124, true)
149
+ std.printf(' [%d] %s\n', i + 1, name)
150
+ std.printf(' port: %s\n', port)
151
+ std.printf(' driver: %s\n', driver)
152
+ if (location) std.printf(' location: %s\n', location)
153
+ if (comment) std.printf(' comment: %s\n', comment)
154
+ std.printf(' status: 0x%08X (%s)\n', status, formatStatus(status))
155
+ }
156
+ } else {
157
+ std.printf(' no printers installed (skip)\n')
158
+ }
159
+
160
+ t.section('pointer arg type check')
161
+ let threw = false
162
+ try {
163
+ ffi.ffiCall(
164
+ enumPrinters,
165
+ [ffi.FFI_TYPE_POINTER] as any,
166
+ [123] as any,
167
+ ffi.FFI_TYPE_VOID
168
+ )
169
+ } catch (e) {
170
+ threw = true
171
+ }
172
+ t.checkTrue('non-ArrayBuffer pointer arg throws TypeError', threw)
173
+ }
174
+ }
@@ -0,0 +1,128 @@
1
+ import * as std from 'std';
2
+ export const suite = {
3
+ name: 'wasm-frame-encoding',
4
+ run: (t) => {
5
+ function createFrameHeader(payloadLen) {
6
+ let headerSize = payloadLen < 126 ? 6 : (payloadLen < 65536 ? 8 : 14);
7
+ let buf = new ArrayBuffer(headerSize);
8
+ let view = new Uint8Array(buf);
9
+ let off = 0;
10
+ view[off++] = 0x81;
11
+ if (payloadLen < 126) {
12
+ view[off++] = 0x80 | payloadLen;
13
+ }
14
+ else if (payloadLen < 65536) {
15
+ view[off++] = 0x80 | 126;
16
+ view[off++] = (payloadLen >> 8) & 0xFF;
17
+ view[off++] = payloadLen & 0xFF;
18
+ }
19
+ else {
20
+ view[off++] = 0x80 | 127;
21
+ const len = payloadLen;
22
+ const hi = Math.floor(len / 0x100000000) >>> 0;
23
+ const lo = len >>> 0;
24
+ view[off++] = (hi >> 24) & 0xFF;
25
+ view[off++] = (hi >> 16) & 0xFF;
26
+ view[off++] = (hi >> 8) & 0xFF;
27
+ view[off++] = hi & 0xFF;
28
+ view[off++] = (lo >> 24) & 0xFF;
29
+ view[off++] = (lo >> 16) & 0xFF;
30
+ view[off++] = (lo >> 8) & 0xFF;
31
+ view[off++] = lo & 0xFF;
32
+ }
33
+ return view;
34
+ }
35
+ function tryParseFrameLen(data) {
36
+ if (data.length < 2)
37
+ return null;
38
+ const b1 = data[1];
39
+ let payloadLen = b1 & 0x7F;
40
+ let offset = 2;
41
+ if (payloadLen === 126) {
42
+ if (data.length < 4)
43
+ return null;
44
+ payloadLen = (data[2] << 8) | data[3];
45
+ }
46
+ else if (payloadLen === 127) {
47
+ if (data.length < 10)
48
+ return null;
49
+ let hi = 0, lo = 0;
50
+ for (let i = 0; i < 4; i++)
51
+ hi = (hi * 256 + data[offset + i]) >>> 0;
52
+ for (let i = 4; i < 8; i++)
53
+ lo = (lo * 256 + data[offset + i]) >>> 0;
54
+ payloadLen = hi * 0x100000000 + lo;
55
+ }
56
+ return payloadLen;
57
+ }
58
+ function assert(name, ok) {
59
+ if (ok) {
60
+ t.ok++;
61
+ std.printf(' PASS: %s\n', name);
62
+ }
63
+ else {
64
+ t.fail++;
65
+ std.printf(' FAIL: %s\n', name);
66
+ }
67
+ }
68
+ // small frame (100 bytes)
69
+ t.section('inline length (<126)');
70
+ let h = createFrameHeader(100);
71
+ assert('length=100', h[1] === (0x80 | 100));
72
+ assert('parse 100', tryParseFrameLen(h) === 100);
73
+ // 1 (edge case)
74
+ h = createFrameHeader(1);
75
+ assert('length=1', h[1] === (0x80 | 1));
76
+ assert('parse 1', tryParseFrameLen(h) === 1);
77
+ // 125 (max inline)
78
+ h = createFrameHeader(125);
79
+ assert('length=125', h[1] === (0x80 | 125));
80
+ assert('parse 125', tryParseFrameLen(h) === 125);
81
+ // 126 (enters 16-bit extended)
82
+ t.section('16-bit extended length (126–65535)');
83
+ h = createFrameHeader(126);
84
+ assert('126 marker', h[1] === (0x80 | 126));
85
+ assert('126 value', h[2] === 0 && h[3] === 126);
86
+ assert('parse 126', tryParseFrameLen(h) === 126);
87
+ // 65535 (max 16-bit)
88
+ h = createFrameHeader(65535);
89
+ assert('65535 marker', h[1] === (0x80 | 126));
90
+ assert('65535 hi byte', h[2] === 0xFF);
91
+ assert('65535 lo byte', h[3] === 0xFF);
92
+ assert('parse 65535', tryParseFrameLen(h) === 65535);
93
+ // 65536 (enters 64-bit extended)
94
+ t.section('64-bit extended length (>=65536)');
95
+ h = createFrameHeader(65536);
96
+ assert('65536 marker', h[1] === (0x80 | 127));
97
+ assert('parse 65536', tryParseFrameLen(h) === 65536);
98
+ // 66000
99
+ h = createFrameHeader(66000);
100
+ assert('66000 marker', h[1] === (0x80 | 127));
101
+ assert('parse 66000', tryParseFrameLen(h) === 66000);
102
+ // 70000 — verify exact bytes
103
+ h = createFrameHeader(70000);
104
+ assert('70000 marker', h[1] === (0x80 | 127));
105
+ // 70000 = 0x11170, big-endian 8 bytes: 00 00 00 00 00 01 11 70
106
+ assert('70000 bytes big-endian', h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x00 &&
107
+ h[6] === 0x00 && h[7] === 0x01 && h[8] === 0x11 && h[9] === 0x70);
108
+ assert('parse 70000', tryParseFrameLen(h) === 70000);
109
+ // Large value near 2^32 boundary
110
+ t.section('large values');
111
+ h = createFrameHeader(0xABCD1234);
112
+ assert('0xABCD1234 marker', h[1] === (0x80 | 127));
113
+ assert('parse 0xABCD1234', tryParseFrameLen(h) === 0xABCD1234);
114
+ assert('MSB non-zero', h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x00 &&
115
+ h[6] === 0xAB && h[7] === 0xCD && h[8] === 0x12 && h[9] === 0x34);
116
+ // 0x100000001 (just above 2^32)
117
+ h = createFrameHeader(0x100000001);
118
+ assert('2^32+1 parse', tryParseFrameLen(h) === 0x100000001);
119
+ assert('2^32+1 bytes', h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x01 &&
120
+ h[6] === 0x00 && h[7] === 0x00 && h[8] === 0x00 && h[9] === 0x01);
121
+ // Full 8-byte roundtrip: 0x0123456789ABCDEF
122
+ // This exceeds Number's safe integer range (2^53) slightly,
123
+ // but our test value 0x0123456789ABCDEF = 81985529216486895 < 2^53 ✓
124
+ const bigVal = 0x0123456789ABCDEF;
125
+ h = createFrameHeader(bigVal);
126
+ assert('big val parse', tryParseFrameLen(h) === bigVal);
127
+ }
128
+ };
@@ -0,0 +1,132 @@
1
+ import * as std from 'std'
2
+ import { Tester } from './test_helper.js'
3
+
4
+ export const suite = {
5
+ name: 'wasm-frame-encoding',
6
+ run: (t: Tester) => {
7
+ function createFrameHeader(payloadLen: number): Uint8Array {
8
+ let headerSize = payloadLen < 126 ? 6 : (payloadLen < 65536 ? 8 : 14)
9
+ let buf = new ArrayBuffer(headerSize)
10
+ let view = new Uint8Array(buf)
11
+ let off = 0
12
+ view[off++] = 0x81
13
+ if (payloadLen < 126) {
14
+ view[off++] = 0x80 | payloadLen
15
+ } else if (payloadLen < 65536) {
16
+ view[off++] = 0x80 | 126
17
+ view[off++] = (payloadLen >> 8) & 0xFF
18
+ view[off++] = payloadLen & 0xFF
19
+ } else {
20
+ view[off++] = 0x80 | 127
21
+ const len = payloadLen
22
+ const hi = Math.floor(len / 0x100000000) >>> 0
23
+ const lo = len >>> 0
24
+ view[off++] = (hi >> 24) & 0xFF
25
+ view[off++] = (hi >> 16) & 0xFF
26
+ view[off++] = (hi >> 8) & 0xFF
27
+ view[off++] = hi & 0xFF
28
+ view[off++] = (lo >> 24) & 0xFF
29
+ view[off++] = (lo >> 16) & 0xFF
30
+ view[off++] = (lo >> 8) & 0xFF
31
+ view[off++] = lo & 0xFF
32
+ }
33
+ return view
34
+ }
35
+
36
+ function tryParseFrameLen(data: Uint8Array): number | null {
37
+ if (data.length < 2) return null
38
+ const b1 = data[1]
39
+ let payloadLen = b1 & 0x7F
40
+ let offset = 2
41
+ if (payloadLen === 126) {
42
+ if (data.length < 4) return null
43
+ payloadLen = (data[2] << 8) | data[3]
44
+ } else if (payloadLen === 127) {
45
+ if (data.length < 10) return null
46
+ let hi = 0, lo = 0
47
+ for (let i = 0; i < 4; i++) hi = (hi * 256 + data[offset + i]) >>> 0
48
+ for (let i = 4; i < 8; i++) lo = (lo * 256 + data[offset + i]) >>> 0
49
+ payloadLen = hi * 0x100000000 + lo
50
+ }
51
+ return payloadLen
52
+ }
53
+
54
+ function assert(name: string, ok: boolean): void {
55
+ if (ok) { t.ok++; std.printf(' PASS: %s\n', name) }
56
+ else { t.fail++; std.printf(' FAIL: %s\n', name) }
57
+ }
58
+
59
+ // small frame (100 bytes)
60
+ t.section('inline length (<126)')
61
+ let h = createFrameHeader(100)
62
+ assert('length=100', h[1] === (0x80 | 100))
63
+ assert('parse 100', tryParseFrameLen(h) === 100)
64
+
65
+ // 1 (edge case)
66
+ h = createFrameHeader(1)
67
+ assert('length=1', h[1] === (0x80 | 1))
68
+ assert('parse 1', tryParseFrameLen(h) === 1)
69
+
70
+ // 125 (max inline)
71
+ h = createFrameHeader(125)
72
+ assert('length=125', h[1] === (0x80 | 125))
73
+ assert('parse 125', tryParseFrameLen(h) === 125)
74
+
75
+ // 126 (enters 16-bit extended)
76
+ t.section('16-bit extended length (126–65535)')
77
+ h = createFrameHeader(126)
78
+ assert('126 marker', h[1] === (0x80 | 126))
79
+ assert('126 value', h[2] === 0 && h[3] === 126)
80
+ assert('parse 126', tryParseFrameLen(h) === 126)
81
+
82
+ // 65535 (max 16-bit)
83
+ h = createFrameHeader(65535)
84
+ assert('65535 marker', h[1] === (0x80 | 126))
85
+ assert('65535 hi byte', h[2] === 0xFF)
86
+ assert('65535 lo byte', h[3] === 0xFF)
87
+ assert('parse 65535', tryParseFrameLen(h) === 65535)
88
+
89
+ // 65536 (enters 64-bit extended)
90
+ t.section('64-bit extended length (>=65536)')
91
+ h = createFrameHeader(65536)
92
+ assert('65536 marker', h[1] === (0x80 | 127))
93
+ assert('parse 65536', tryParseFrameLen(h) === 65536)
94
+
95
+ // 66000
96
+ h = createFrameHeader(66000)
97
+ assert('66000 marker', h[1] === (0x80 | 127))
98
+ assert('parse 66000', tryParseFrameLen(h) === 66000)
99
+
100
+ // 70000 — verify exact bytes
101
+ h = createFrameHeader(70000)
102
+ assert('70000 marker', h[1] === (0x80 | 127))
103
+ // 70000 = 0x11170, big-endian 8 bytes: 00 00 00 00 00 01 11 70
104
+ assert('70000 bytes big-endian',
105
+ h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x00 &&
106
+ h[6] === 0x00 && h[7] === 0x01 && h[8] === 0x11 && h[9] === 0x70)
107
+ assert('parse 70000', tryParseFrameLen(h) === 70000)
108
+
109
+ // Large value near 2^32 boundary
110
+ t.section('large values')
111
+ h = createFrameHeader(0xABCD1234)
112
+ assert('0xABCD1234 marker', h[1] === (0x80 | 127))
113
+ assert('parse 0xABCD1234', tryParseFrameLen(h) === 0xABCD1234)
114
+ assert('MSB non-zero',
115
+ h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x00 &&
116
+ h[6] === 0xAB && h[7] === 0xCD && h[8] === 0x12 && h[9] === 0x34)
117
+
118
+ // 0x100000001 (just above 2^32)
119
+ h = createFrameHeader(0x100000001)
120
+ assert('2^32+1 parse', tryParseFrameLen(h) === 0x100000001)
121
+ assert('2^32+1 bytes',
122
+ h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x01 &&
123
+ h[6] === 0x00 && h[7] === 0x00 && h[8] === 0x00 && h[9] === 0x01)
124
+
125
+ // Full 8-byte roundtrip: 0x0123456789ABCDEF
126
+ // This exceeds Number's safe integer range (2^53) slightly,
127
+ // but our test value 0x0123456789ABCDEF = 81985529216486895 < 2^53 ✓
128
+ const bigVal = 0x0123456789ABCDEF
129
+ h = createFrameHeader(bigVal)
130
+ assert('big val parse', tryParseFrameLen(h) === bigVal)
131
+ }
132
+ }
@@ -0,0 +1,84 @@
1
+ import * as std from 'std';
2
+ const GREEN = '\x1b[32m';
3
+ const RED = '\x1b[31m';
4
+ const BOLD = '\x1b[1m';
5
+ const RESET = '\x1b[0m';
6
+ function formatDuration(ms) {
7
+ if (ms >= 1000)
8
+ return (ms / 1000).toFixed(2) + 's';
9
+ return ms + 'ms';
10
+ }
11
+ export function readWasmFile(path) {
12
+ const base = import.meta.url.slice(0, import.meta.url.lastIndexOf('/') + 1);
13
+ const parts = (base + path).split('/');
14
+ const out = [];
15
+ for (const p of parts) {
16
+ if (p === '..')
17
+ out.pop();
18
+ else if (p !== '.')
19
+ out.push(p);
20
+ }
21
+ let filePath = out.join('/').slice(7);
22
+ if (filePath.length >= 3 && filePath[1] === ':')
23
+ filePath = filePath.slice(1);
24
+ const fp = std.open(filePath, 'rb');
25
+ if (!fp)
26
+ return null;
27
+ fp.seek(0, 2);
28
+ const size = fp.tell();
29
+ fp.seek(0, 0);
30
+ const buffer = new ArrayBuffer(size);
31
+ fp.read(buffer, 0, size);
32
+ fp.close();
33
+ return buffer;
34
+ }
35
+ export class Tester {
36
+ ok = 0;
37
+ fail = 0;
38
+ startTime = Date.now();
39
+ sectionStart = this.startTime;
40
+ lastSection = '';
41
+ section(name) {
42
+ const now = Date.now();
43
+ if (this.lastSection) {
44
+ const elapsed = now - this.sectionStart;
45
+ std.printf(' (%s)\n', formatDuration(elapsed));
46
+ }
47
+ this.sectionStart = now;
48
+ this.lastSection = name;
49
+ std.printf('\n%s=== %s ===%s\n', BOLD, name, RESET);
50
+ }
51
+ check(name, expected, actual) {
52
+ if (typeof expected === 'number' && typeof actual === 'number') {
53
+ if (Math.abs(expected - actual) < 0.0001) {
54
+ this.ok++;
55
+ std.printf(' %sPASS:%s %s = %s\n', GREEN, RESET, name, String(actual));
56
+ }
57
+ else {
58
+ this.fail++;
59
+ std.printf(' %sFAIL:%s %s = %s (expected %s)\n', RED, RESET, name, String(actual), String(expected));
60
+ }
61
+ }
62
+ else if (expected === actual) {
63
+ this.ok++;
64
+ std.printf(' %sPASS:%s %s = %s\n', GREEN, RESET, name, String(actual));
65
+ }
66
+ else {
67
+ this.fail++;
68
+ std.printf(' %sFAIL:%s %s = %s (expected %s)\n', RED, RESET, name, String(actual), String(expected));
69
+ }
70
+ }
71
+ checkTrue(name, actual) {
72
+ this.check(name, true, actual);
73
+ }
74
+ summary() {
75
+ const now = Date.now();
76
+ if (this.lastSection) {
77
+ const elapsed = now - this.sectionStart;
78
+ std.printf(' (%s)\n', formatDuration(elapsed));
79
+ }
80
+ const total = now - this.startTime;
81
+ const color = this.fail > 0 ? RED : GREEN;
82
+ std.printf('\n%s%d/%d passed (%s)%s\n', color, this.ok, this.ok + this.fail, formatDuration(total), RESET);
83
+ }
84
+ }