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.
- package/README.md +6 -0
- package/examples/pdf_preview.js +440 -0
- package/examples/pdf_preview.ts +470 -0
- package/examples/preact_demo.js +35 -0
- package/examples/preact_demo.tsx +49 -0
- package/examples/tray_demo.js +75 -0
- package/examples/tray_demo.tsx +79 -0
- package/lib/fetch.js +746 -0
- package/lib/fetch.ts +811 -0
- package/lib/polyfill.js +500 -0
- package/lib/polyfill.ts +454 -0
- package/lib/preact/hooks.js +287 -0
- package/lib/preact/hooks.ts +330 -0
- package/lib/preact/jsx-runtime.js +1 -0
- package/lib/preact/jsx-runtime.ts +2 -0
- package/lib/preact/jsx.d.ts +36 -0
- package/lib/preact/layout.js +153 -0
- package/lib/preact/layout.ts +183 -0
- package/lib/preact/preact.js +54 -0
- package/lib/preact/preact.ts +133 -0
- package/lib/preact/props.js +99 -0
- package/lib/preact/props.ts +119 -0
- package/lib/preact/render.js +320 -0
- package/lib/preact/render.ts +353 -0
- package/lib/websocket.js +540 -0
- package/lib/websocket.ts +574 -0
- package/package.json +32 -0
- package/quickwin.d.ts +657 -0
- package/test/add.wasm +0 -0
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/global_imports.wasm +0 -0
- package/test/import_func.wasm +0 -0
- package/test/imports.wasm +0 -0
- package/test/run.js +86 -0
- package/test/run.ts +90 -0
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +7 -0
- package/test/test_basic.ts +9 -0
- package/test/test_brotli.js +48 -0
- package/test/test_brotli.ts +52 -0
- package/test/test_fetch_cache.js +131 -0
- package/test/test_fetch_cache.ts +141 -0
- package/test/test_ffi.js +157 -0
- package/test/test_ffi.ts +174 -0
- package/test/test_frame_encoding.js +128 -0
- package/test/test_frame_encoding.ts +132 -0
- package/test/test_helper.js +84 -0
- package/test/test_helper.ts +80 -0
- package/test/test_http_import.js +78 -0
- package/test/test_http_import.ts +74 -0
- package/test/test_mupdf_render.js +69 -0
- package/test/test_mupdf_render.ts +74 -0
- package/test/test_mupdf_twice.js +77 -0
- package/test/test_mupdf_twice.ts +81 -0
- package/test/test_mupdf_wasm.js +33 -0
- package/test/test_mupdf_wasm.ts +30 -0
- package/test/test_net_event.js +63 -0
- package/test/test_net_event.ts +59 -0
- package/test/test_net_fetch.js +153 -0
- package/test/test_net_fetch.ts +131 -0
- package/test/test_net_websocket.js +158 -0
- package/test/test_net_websocket.ts +144 -0
- package/test/test_polyfill.js +58 -0
- package/test/test_polyfill.ts +60 -0
- package/test/test_url.js +173 -0
- package/test/test_url.ts +183 -0
- package/test/test_wasm_basic.js +82 -0
- package/test/test_wasm_basic.ts +70 -0
- package/test/test_wasm_import_global.js +41 -0
- package/test/test_wasm_import_global.ts +39 -0
- package/test/test_wasm_sjlj.js +153 -0
- package/test/test_wasm_sjlj.ts +134 -0
- package/test/test_wasm_types.js +96 -0
- package/test/test_wasm_types.ts +108 -0
- package/test/types.wasm +0 -0
- package/tsconfig.json +18 -0
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
- package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
- package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
- package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
- package/vendor/mupdf-wasm/mupdf.js +3317 -0
- package/win-mingw64.exe +0 -0
package/test/test_ffi.js
ADDED
|
@@ -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
|
+
};
|
package/test/test_ffi.ts
ADDED
|
@@ -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
|
+
}
|