toiljs 0.0.124 → 0.0.125
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 +14 -10
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2 -2
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/auth.d.ts +2 -0
- package/build/client/auth.js +7 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/generate.js +1 -1
- package/build/compiler/index.js +2 -0
- package/build/compiler/toil-docs.generated.js +11 -11
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/daemon/index.d.ts +4 -0
- package/build/devserver/daemon/index.js +84 -2
- package/build/devserver/db/database.d.ts +11 -2
- package/build/devserver/db/database.js +227 -22
- package/build/devserver/db/limits.d.ts +6 -0
- package/build/devserver/db/limits.js +118 -0
- package/build/devserver/db/materialization.d.ts +4 -0
- package/build/devserver/db/materialization.js +25 -0
- package/build/devserver/db/types.d.ts +3 -0
- package/build/devserver/db/types.js +1 -0
- package/build/devserver/runtime/host.js +17 -0
- package/build/devserver/runtime/module.d.ts +4 -0
- package/build/devserver/runtime/module.js +47 -9
- package/build/devserver/wasm/envelope-window.d.ts +1 -0
- package/build/devserver/wasm/envelope-window.js +64 -0
- package/build/testing/.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/server/auth/AuthController.ts +265 -61
- package/server/runtime/response.ts +12 -4
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { DbFunctionKind } from './types.js';
|
|
2
|
+
const counters = new WeakMap();
|
|
3
|
+
const exempt = new Set([
|
|
4
|
+
'data.resolve_collection',
|
|
5
|
+
'data.take_result',
|
|
6
|
+
'data.result_schema_version',
|
|
7
|
+
'data.write_allowed',
|
|
8
|
+
]);
|
|
9
|
+
const reads = new Set([
|
|
10
|
+
'data.get',
|
|
11
|
+
'data.get_current',
|
|
12
|
+
'data.get_many',
|
|
13
|
+
'data.exists',
|
|
14
|
+
'data.unique_lookup',
|
|
15
|
+
'data.view_get',
|
|
16
|
+
'data.membership_contains',
|
|
17
|
+
'data.membership_list',
|
|
18
|
+
'data.membership_page',
|
|
19
|
+
'data.counter_get',
|
|
20
|
+
'data.events_get',
|
|
21
|
+
'data.events_reference',
|
|
22
|
+
'data.events_entries',
|
|
23
|
+
'data.events_last',
|
|
24
|
+
'data.latest',
|
|
25
|
+
'data.events_since',
|
|
26
|
+
'data.capacity_available',
|
|
27
|
+
]);
|
|
28
|
+
const results = new Set([
|
|
29
|
+
'data.get',
|
|
30
|
+
'data.get_current',
|
|
31
|
+
'data.get_many',
|
|
32
|
+
'data.get_delete',
|
|
33
|
+
'data.patch',
|
|
34
|
+
'data.unique_lookup',
|
|
35
|
+
'data.view_get',
|
|
36
|
+
'data.membership_list',
|
|
37
|
+
'data.membership_page',
|
|
38
|
+
'data.counter_get',
|
|
39
|
+
'data.events_get',
|
|
40
|
+
'data.events_reference',
|
|
41
|
+
'data.events_entries',
|
|
42
|
+
'data.events_last',
|
|
43
|
+
'data.latest',
|
|
44
|
+
'data.events_since',
|
|
45
|
+
'data.capacity_available',
|
|
46
|
+
'data.capacity_reserve',
|
|
47
|
+
]);
|
|
48
|
+
const writeLength = {
|
|
49
|
+
'data.create': 4,
|
|
50
|
+
'data.patch': 4,
|
|
51
|
+
'data.upsert': 4,
|
|
52
|
+
'data.enqueue': 4,
|
|
53
|
+
'data.compare_exchange': 6,
|
|
54
|
+
'data.compare_delete': 4,
|
|
55
|
+
'data.unique_claim': 4,
|
|
56
|
+
'data.view_publish': 4,
|
|
57
|
+
'data.membership_add': 4,
|
|
58
|
+
'data.append': 4,
|
|
59
|
+
'data.append_once': 6,
|
|
60
|
+
};
|
|
61
|
+
export function resetDbLimits(db) {
|
|
62
|
+
counters.delete(db);
|
|
63
|
+
}
|
|
64
|
+
export function checkDbResultSize(db, bytes) {
|
|
65
|
+
if (process.env.TOIL_DEV_CHECK_DB_LIMITS !== '1')
|
|
66
|
+
return;
|
|
67
|
+
const max = db.functionKind === DbFunctionKind.Query
|
|
68
|
+
? 65536
|
|
69
|
+
: db.functionKind === DbFunctionKind.Action
|
|
70
|
+
? 131072
|
|
71
|
+
: 1048576;
|
|
72
|
+
if (bytes > max)
|
|
73
|
+
throw new Error(`TDL021: ${db.functionKind} DB operation uses ${String(bytes)} bytes; maximum is ${String(max)}`);
|
|
74
|
+
}
|
|
75
|
+
export function withDbLimits(imports, db) {
|
|
76
|
+
return Object.fromEntries(Object.entries(imports).map(([name, operation]) => [
|
|
77
|
+
name,
|
|
78
|
+
(...args) => {
|
|
79
|
+
if (process.env.TOIL_DEV_CHECK_DB_LIMITS !== '1' || exempt.has(name))
|
|
80
|
+
return operation(...args);
|
|
81
|
+
const kind = db.functionKind;
|
|
82
|
+
let count = counters.get(db);
|
|
83
|
+
if (count === undefined || count.kind !== kind) {
|
|
84
|
+
count = { kind, ops: 0, writes: 0, bytes: 0 };
|
|
85
|
+
counters.set(db, count);
|
|
86
|
+
}
|
|
87
|
+
const maxOps = kind === DbFunctionKind.Query || kind === DbFunctionKind.Action ? 64 : 65535;
|
|
88
|
+
const maxWrites = kind === DbFunctionKind.Query ? 0 : kind === DbFunctionKind.Action ? 32 : 65535;
|
|
89
|
+
const maxBytes = kind === DbFunctionKind.Query
|
|
90
|
+
? 131072
|
|
91
|
+
: kind === DbFunctionKind.Action
|
|
92
|
+
? 2097152
|
|
93
|
+
: 16777216;
|
|
94
|
+
const writing = !reads.has(name);
|
|
95
|
+
if (++count.ops > maxOps || (writing && ++count.writes > maxWrites))
|
|
96
|
+
throw new Error(`TDL020: ${name} exceeds ${kind} DB budget (${String(count.ops)}/${String(maxOps)} operations, ${String(count.writes)}/${String(maxWrites)} writes)`);
|
|
97
|
+
const writeBytes = (name === 'data.events_redact' ? 16 : writeLength[name] === undefined ? 0 : args[writeLength[name]]) +
|
|
98
|
+
(name === 'data.compare_exchange' ? Math.max(0, args[4]) : 0);
|
|
99
|
+
checkDbResultSize(db, writeBytes);
|
|
100
|
+
if (count.bytes + writeBytes > maxBytes)
|
|
101
|
+
throw new Error(`TDL021: ${kind} cumulative DB byte budget exceeds ${String(maxBytes)}`);
|
|
102
|
+
count.bytes += writeBytes;
|
|
103
|
+
const status = operation(...args);
|
|
104
|
+
if (typeof status === 'number' && status >= 0) {
|
|
105
|
+
const bytes = results.has(name)
|
|
106
|
+
? (db.lastResult?.length ?? 0)
|
|
107
|
+
: name === 'data.unique_claim' && status === 1
|
|
108
|
+
? (db.lastResult?.length ?? 0)
|
|
109
|
+
: 0;
|
|
110
|
+
checkDbResultSize(db, bytes);
|
|
111
|
+
if (count.bytes + bytes > maxBytes)
|
|
112
|
+
throw new Error(`TDL021: ${kind} cumulative DB byte budget exceeds ${String(maxBytes)}`);
|
|
113
|
+
count.bytes += bytes;
|
|
114
|
+
}
|
|
115
|
+
return status;
|
|
116
|
+
},
|
|
117
|
+
]));
|
|
118
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function registerMaterializer(key: string, runner: (written: ReadonlySet<string>) => void): void;
|
|
2
|
+
export declare function unregisterMaterializer(key: string): void;
|
|
3
|
+
export declare function queueMaterialization(collection: string): void;
|
|
4
|
+
export declare function flushMaterializations(): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const runners = new Map();
|
|
2
|
+
const pending = new Set();
|
|
3
|
+
let scheduled = false;
|
|
4
|
+
export function registerMaterializer(key, runner) {
|
|
5
|
+
runners.set(key, runner);
|
|
6
|
+
}
|
|
7
|
+
export function unregisterMaterializer(key) {
|
|
8
|
+
runners.delete(key);
|
|
9
|
+
}
|
|
10
|
+
export function queueMaterialization(collection) {
|
|
11
|
+
pending.add(collection);
|
|
12
|
+
if (scheduled)
|
|
13
|
+
return;
|
|
14
|
+
scheduled = true;
|
|
15
|
+
queueMicrotask(flushMaterializations);
|
|
16
|
+
}
|
|
17
|
+
export function flushMaterializations() {
|
|
18
|
+
scheduled = false;
|
|
19
|
+
if (pending.size === 0)
|
|
20
|
+
return;
|
|
21
|
+
const written = new Set(pending);
|
|
22
|
+
pending.clear();
|
|
23
|
+
for (const runner of runners.values())
|
|
24
|
+
runner(written);
|
|
25
|
+
}
|
|
@@ -40,6 +40,7 @@ export type DbCatalogState = {
|
|
|
40
40
|
collections: Map<string, DevCollectionHandle>;
|
|
41
41
|
};
|
|
42
42
|
export interface DbDevState {
|
|
43
|
+
logMessages: number;
|
|
43
44
|
handles: DevCollectionHandle[];
|
|
44
45
|
lastResult: Buffer | null;
|
|
45
46
|
lastResultVersion: number;
|
|
@@ -83,7 +84,9 @@ export interface DbSnapshot {
|
|
|
83
84
|
events: Record<string, {
|
|
84
85
|
v: string;
|
|
85
86
|
sv: number;
|
|
87
|
+
hlc?: string;
|
|
86
88
|
}[]>;
|
|
89
|
+
erasedEvents?: Record<string, string[]>;
|
|
87
90
|
eventDedup: Record<string, string[]>;
|
|
88
91
|
eventPositions?: Record<string, [string, number][]>;
|
|
89
92
|
deriveCheckpoints?: Record<string, number>;
|
|
@@ -130,8 +130,24 @@ function devEmailSend(ref, reqPtr, reqLen) {
|
|
|
130
130
|
});
|
|
131
131
|
return EmailStatus.Sent;
|
|
132
132
|
}
|
|
133
|
+
function buildLogImports(ref, db) {
|
|
134
|
+
return {
|
|
135
|
+
server_log: (level, ptr, len) => {
|
|
136
|
+
if (!Number.isInteger(level) || level < 0 || level > 2 || len < 0 || len > 4096)
|
|
137
|
+
throw new Error('server_log: level must be 0..2 and message at most 4096 UTF-8 bytes');
|
|
138
|
+
const message = new TextDecoder('utf-8', { fatal: true }).decode(readBytes(ref, ptr, len));
|
|
139
|
+
if (db.logMessages >= 32)
|
|
140
|
+
return 0;
|
|
141
|
+
db.logMessages++;
|
|
142
|
+
const write = level === 0 ? console.info : level === 1 ? console.warn : console.error;
|
|
143
|
+
write('[toil guest]', message);
|
|
144
|
+
return 1;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
133
148
|
export function buildEnvImports(ref, _state) {
|
|
134
149
|
return {
|
|
150
|
+
...buildLogImports(ref, _state.db),
|
|
135
151
|
abort: (msgPtr, filePtr, line, col) => {
|
|
136
152
|
throw new WasmAbortError(readGuestString(ref, msgPtr), readGuestString(ref, filePtr), line, col);
|
|
137
153
|
},
|
|
@@ -146,6 +162,7 @@ export function buildEnvImports(ref, _state) {
|
|
|
146
162
|
export function buildHostImports(ref, state) {
|
|
147
163
|
return {
|
|
148
164
|
env: {
|
|
165
|
+
...buildLogImports(ref, state.db),
|
|
149
166
|
abort: (msgPtr, filePtr, line, col) => {
|
|
150
167
|
throw new WasmAbortError(readGuestString(ref, msgPtr), readGuestString(ref, filePtr), line, col);
|
|
151
168
|
},
|
|
@@ -19,6 +19,8 @@ export declare class WasmServerModule {
|
|
|
19
19
|
private rpcKinds;
|
|
20
20
|
private derives;
|
|
21
21
|
private derivesDirty;
|
|
22
|
+
private envelopeWindow;
|
|
23
|
+
private readonly pendingDerives;
|
|
22
24
|
constructor(wasmPath: string);
|
|
23
25
|
get available(): boolean;
|
|
24
26
|
refresh(): boolean;
|
|
@@ -27,6 +29,8 @@ export declare class WasmServerModule {
|
|
|
27
29
|
private runAffectedDerives;
|
|
28
30
|
private rebuildDerivedViewsIfStale;
|
|
29
31
|
private runDerive;
|
|
32
|
+
private requestEnvelope;
|
|
33
|
+
private writeRequestEnvelope;
|
|
30
34
|
private assertImportSurface;
|
|
31
35
|
private assertExportSurface;
|
|
32
36
|
}
|
|
@@ -3,6 +3,8 @@ import { commitDeriveCheckpoints, DbFunctionKind, derivesForWrites, parseDerives
|
|
|
3
3
|
import { parseRouteKinds, parseRpcKinds, routeKindForRequest, rpcKindForId, } from '../db/routeKinds.js';
|
|
4
4
|
import { decodeResponseEnvelope, encodeRequestEnvelope, unpackHandleResult, } from '../http/envelope.js';
|
|
5
5
|
import { buildHostImports, freshDispatchState } from './host.js';
|
|
6
|
+
import { envelopeWindowOf } from '../wasm/envelope-window.js';
|
|
7
|
+
import { registerMaterializer, unregisterMaterializer } from '../db/materialization.js';
|
|
6
8
|
export { WasmAbortError } from './host.js';
|
|
7
9
|
export const UNHANDLED_HEADER = 'x-toil-unhandled';
|
|
8
10
|
const WASM_PAGE = 65536;
|
|
@@ -31,6 +33,7 @@ const PROVIDED_IMPORTS = new Set([
|
|
|
31
33
|
'respond_file',
|
|
32
34
|
'thread_spawn',
|
|
33
35
|
'Date.now',
|
|
36
|
+
'server_log',
|
|
34
37
|
'client_ip',
|
|
35
38
|
'ratelimit_check',
|
|
36
39
|
'email_send',
|
|
@@ -68,20 +71,26 @@ const PROVIDED_IMPORTS = new Set([
|
|
|
68
71
|
'data.unique_release',
|
|
69
72
|
'data.view_get',
|
|
70
73
|
'data.view_publish',
|
|
74
|
+
'data.view_delete',
|
|
71
75
|
'data.membership_contains',
|
|
72
76
|
'data.membership_add',
|
|
73
77
|
'data.membership_remove',
|
|
74
78
|
'data.membership_list',
|
|
79
|
+
'data.membership_page',
|
|
75
80
|
'data.counter_get',
|
|
76
81
|
'data.counter_add',
|
|
77
82
|
'data.append',
|
|
78
83
|
'data.append_once',
|
|
79
84
|
'data.enqueue',
|
|
80
85
|
'data.compare_exchange',
|
|
86
|
+
'data.compare_delete',
|
|
81
87
|
'data.get_current',
|
|
82
88
|
'data.latest',
|
|
83
89
|
'data.events_since',
|
|
84
90
|
'data.events_get',
|
|
91
|
+
'data.events_reference',
|
|
92
|
+
'data.events_redact',
|
|
93
|
+
'data.events_entries',
|
|
85
94
|
'data.events_last',
|
|
86
95
|
'data.capacity_set_total',
|
|
87
96
|
'data.capacity_available',
|
|
@@ -100,6 +109,8 @@ export class WasmServerModule {
|
|
|
100
109
|
rpcKinds = [];
|
|
101
110
|
derives = [];
|
|
102
111
|
derivesDirty = false;
|
|
112
|
+
envelopeWindow = null;
|
|
113
|
+
pendingDerives = new Set();
|
|
103
114
|
constructor(wasmPath) {
|
|
104
115
|
this.wasmPath = wasmPath;
|
|
105
116
|
}
|
|
@@ -112,6 +123,7 @@ export class WasmServerModule {
|
|
|
112
123
|
mtimeMs = fs.statSync(this.wasmPath).mtimeMs;
|
|
113
124
|
}
|
|
114
125
|
catch {
|
|
126
|
+
unregisterMaterializer(this.wasmPath);
|
|
115
127
|
this.module = null;
|
|
116
128
|
this.routeKinds = [];
|
|
117
129
|
this.rpcKinds = [];
|
|
@@ -123,6 +135,7 @@ export class WasmServerModule {
|
|
|
123
135
|
if (this.module !== null && mtimeMs === this.loadedMtimeMs)
|
|
124
136
|
return false;
|
|
125
137
|
const bytes = fs.readFileSync(this.wasmPath);
|
|
138
|
+
this.envelopeWindow = envelopeWindowOf(bytes);
|
|
126
139
|
const module = new WebAssembly.Module(bytes);
|
|
127
140
|
this.assertImportSurface(module);
|
|
128
141
|
this.assertExportSurface(module);
|
|
@@ -133,13 +146,21 @@ export class WasmServerModule {
|
|
|
133
146
|
this.module = module;
|
|
134
147
|
this.loadedMtimeMs = mtimeMs;
|
|
135
148
|
this.derivesDirty = this.derives.length > 0;
|
|
149
|
+
registerMaterializer(this.wasmPath, (written) => {
|
|
150
|
+
if (!fs.existsSync(this.wasmPath)) {
|
|
151
|
+
unregisterMaterializer(this.wasmPath);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.runAffectedDerives(written);
|
|
155
|
+
persistDb();
|
|
156
|
+
});
|
|
136
157
|
return true;
|
|
137
158
|
}
|
|
138
159
|
dispatch(req) {
|
|
139
160
|
if (this.module === null)
|
|
140
161
|
throw new Error(`server wasm not loaded (${this.wasmPath})`);
|
|
141
162
|
this.rebuildDerivedViewsIfStale();
|
|
142
|
-
const envelope =
|
|
163
|
+
const envelope = this.requestEnvelope(req);
|
|
143
164
|
const ref = { memory: null };
|
|
144
165
|
const state = freshDispatchState();
|
|
145
166
|
state.clientIp = req.clientIp ?? '';
|
|
@@ -163,9 +184,7 @@ export class WasmServerModule {
|
|
|
163
184
|
const instance = new WebAssembly.Instance(this.module, buildHostImports(ref, state));
|
|
164
185
|
const exports = instance.exports;
|
|
165
186
|
ref.memory = exports.memory;
|
|
166
|
-
const reqOfs = exports.memory
|
|
167
|
-
exports.memory.grow(Math.ceil(envelope.length / WASM_PAGE) || 1);
|
|
168
|
-
Buffer.from(exports.memory.buffer).set(envelope, reqOfs);
|
|
187
|
+
const reqOfs = this.writeRequestEnvelope(exports.memory, envelope);
|
|
169
188
|
const packed = exports.handle(reqOfs, envelope.length);
|
|
170
189
|
const { ptr, len } = unpackHandleResult(packed);
|
|
171
190
|
const memSize = exports.memory.buffer.byteLength;
|
|
@@ -188,7 +207,8 @@ export class WasmServerModule {
|
|
|
188
207
|
dispatchRender(req) {
|
|
189
208
|
if (this.module === null)
|
|
190
209
|
throw new Error(`server wasm not loaded (${this.wasmPath})`);
|
|
191
|
-
|
|
210
|
+
this.rebuildDerivedViewsIfStale();
|
|
211
|
+
const envelope = this.requestEnvelope(req);
|
|
192
212
|
const ref = { memory: null };
|
|
193
213
|
const state = freshDispatchState();
|
|
194
214
|
state.clientIp = req.clientIp ?? '';
|
|
@@ -198,9 +218,7 @@ export class WasmServerModule {
|
|
|
198
218
|
if (typeof exports.render !== 'function')
|
|
199
219
|
throw new Error(`guest wasm has no 'render' export (${this.wasmPath})`);
|
|
200
220
|
ref.memory = exports.memory;
|
|
201
|
-
const reqOfs = exports.memory
|
|
202
|
-
exports.memory.grow(Math.ceil(envelope.length / WASM_PAGE) || 1);
|
|
203
|
-
Buffer.from(exports.memory.buffer).set(envelope, reqOfs);
|
|
221
|
+
const reqOfs = this.writeRequestEnvelope(exports.memory, envelope);
|
|
204
222
|
const packed = exports.render(reqOfs, envelope.length);
|
|
205
223
|
const { ptr, len } = unpackHandleResult(packed);
|
|
206
224
|
const memSize = exports.memory.buffer.byteLength;
|
|
@@ -211,9 +229,13 @@ export class WasmServerModule {
|
|
|
211
229
|
runAffectedDerives(written) {
|
|
212
230
|
if (this.module === null)
|
|
213
231
|
return;
|
|
214
|
-
|
|
232
|
+
const affected = derivesForWrites(this.derives, written);
|
|
233
|
+
for (const derive of affected)
|
|
234
|
+
this.pendingDerives.add(derive.deriveId);
|
|
235
|
+
for (const derive of this.derives.filter((d) => this.pendingDerives.has(d.deriveId))) {
|
|
215
236
|
try {
|
|
216
237
|
this.runDerive(derive.deriveId);
|
|
238
|
+
this.pendingDerives.delete(derive.deriveId);
|
|
217
239
|
}
|
|
218
240
|
catch (err) {
|
|
219
241
|
console.error(`[toil] derive ${derive.dbName}#${derive.methodName} failed:`, err);
|
|
@@ -221,6 +243,7 @@ export class WasmServerModule {
|
|
|
221
243
|
}
|
|
222
244
|
}
|
|
223
245
|
rebuildDerivedViewsIfStale() {
|
|
246
|
+
this.runAffectedDerives(new Set());
|
|
224
247
|
if (!this.derivesDirty)
|
|
225
248
|
return;
|
|
226
249
|
this.derivesDirty = false;
|
|
@@ -248,6 +271,21 @@ export class WasmServerModule {
|
|
|
248
271
|
exports.derive_run(deriveId);
|
|
249
272
|
commitDeriveCheckpoints(state.db);
|
|
250
273
|
}
|
|
274
|
+
requestEnvelope(req) {
|
|
275
|
+
const envelope = encodeRequestEnvelope(req);
|
|
276
|
+
if (envelope.length > 8 * 1024 * 1024)
|
|
277
|
+
throw new Error('request envelope exceeds the native 8 MiB request limit');
|
|
278
|
+
return envelope;
|
|
279
|
+
}
|
|
280
|
+
writeRequestEnvelope(memory, envelope) {
|
|
281
|
+
const fits = this.envelopeWindow === null || envelope.length <= this.envelopeWindow;
|
|
282
|
+
const offset = fits ? 0 : memory.buffer.byteLength;
|
|
283
|
+
const needed = offset + envelope.length;
|
|
284
|
+
if (needed > memory.buffer.byteLength)
|
|
285
|
+
memory.grow(Math.ceil((needed - memory.buffer.byteLength) / WASM_PAGE));
|
|
286
|
+
Buffer.from(memory.buffer).set(envelope, offset);
|
|
287
|
+
return offset;
|
|
288
|
+
}
|
|
251
289
|
assertImportSurface(module) {
|
|
252
290
|
const missing = WebAssembly.Module.imports(module)
|
|
253
291
|
.filter((i) => i.kind === 'function' && (i.module !== 'env' || !PROVIDED_IMPORTS.has(i.name)))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function envelopeWindowOf(wasm: Buffer): number | null;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function envelopeWindowOf(wasm) {
|
|
2
|
+
if (wasm.length < 8 || !wasm.subarray(0, 8).equals(Buffer.from([0, 97, 115, 109, 1, 0, 0, 0])))
|
|
3
|
+
throw new Error('invalid wasm header');
|
|
4
|
+
let pos = 8;
|
|
5
|
+
let end = wasm.length;
|
|
6
|
+
let window = null;
|
|
7
|
+
const byte = () => {
|
|
8
|
+
if (pos >= end)
|
|
9
|
+
throw new Error('truncated wasm data section');
|
|
10
|
+
return wasm[pos++];
|
|
11
|
+
};
|
|
12
|
+
const integer = (signed = false) => {
|
|
13
|
+
let value = 0;
|
|
14
|
+
let shift = 0;
|
|
15
|
+
for (let i = 0; i < 5; i++) {
|
|
16
|
+
const b = byte();
|
|
17
|
+
value += (b & 127) * 2 ** shift;
|
|
18
|
+
shift += 7;
|
|
19
|
+
if ((b & 128) === 0) {
|
|
20
|
+
if (signed && b & 64)
|
|
21
|
+
value -= 2 ** shift;
|
|
22
|
+
if (value < (signed ? -0x80000000 : 0) ||
|
|
23
|
+
value > (signed ? 0x7fffffff : 0xffffffff))
|
|
24
|
+
throw new Error('invalid wasm data integer');
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
throw new Error('invalid wasm data integer');
|
|
29
|
+
};
|
|
30
|
+
while (pos < wasm.length) {
|
|
31
|
+
end = wasm.length;
|
|
32
|
+
const id = byte();
|
|
33
|
+
const size = integer();
|
|
34
|
+
end = pos + size;
|
|
35
|
+
if (end > wasm.length)
|
|
36
|
+
throw new Error('truncated wasm section');
|
|
37
|
+
if (id === 11) {
|
|
38
|
+
const count = integer();
|
|
39
|
+
for (let i = 0; i < count; i++) {
|
|
40
|
+
const flags = integer();
|
|
41
|
+
if (flags === 0 || flags === 2) {
|
|
42
|
+
if (flags === 2 && integer() !== 0)
|
|
43
|
+
throw new Error('unsupported data memory');
|
|
44
|
+
if (byte() !== 0x41)
|
|
45
|
+
throw new Error('data offset must be a constant i32');
|
|
46
|
+
const offset = integer(true);
|
|
47
|
+
if (offset < 0 || byte() !== 0x0b)
|
|
48
|
+
throw new Error('invalid data offset');
|
|
49
|
+
window = window === null ? offset : Math.min(window, offset);
|
|
50
|
+
}
|
|
51
|
+
else if (flags !== 1)
|
|
52
|
+
throw new Error('unsupported data segment flags');
|
|
53
|
+
const length = integer();
|
|
54
|
+
pos += length;
|
|
55
|
+
if (pos > end)
|
|
56
|
+
throw new Error('truncated wasm data segment');
|
|
57
|
+
}
|
|
58
|
+
if (pos !== end)
|
|
59
|
+
throw new Error('invalid wasm data section framing');
|
|
60
|
+
}
|
|
61
|
+
pos = end;
|
|
62
|
+
}
|
|
63
|
+
return window;
|
|
64
|
+
}
|