@aresdefencelabs/wasm-http-runtime 0.0.3 → 0.0.5
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/dist/abi.d.ts +2 -0
- package/dist/abi.js +23 -6
- package/dist/memory.js +4 -1
- package/dist/runtime.d.ts +9 -0
- package/dist/runtime.js +116 -18
- package/dist/types.d.ts +8 -1
- package/package.json +1 -1
package/dist/abi.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { RuntimeState } from "./types";
|
|
2
2
|
export declare function createAresAbiImports<Env>(state: RuntimeState<Env>): {
|
|
3
3
|
env: {
|
|
4
|
+
alloc(size: number): number;
|
|
5
|
+
free_mem(ptr: number, size: number): void;
|
|
4
6
|
abi_log(messagePtr: number): void;
|
|
5
7
|
abi_http_get_user_agent_name(): number;
|
|
6
8
|
abi_http_fetch_blocking(requestJsonCstrPtr: number): Promise<number>;
|
package/dist/abi.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { encodeUtf8, readCString, utf8ByteLength, writeBytesIntoMemory, writeCString } from "./memory";
|
|
2
|
+
import { getAllocOrThrow, getFreeOrThrow, getMemoryOrThrow } from "./runtime";
|
|
2
3
|
function getInstanceOrThrow(state) {
|
|
3
4
|
if (!state.instance) {
|
|
4
5
|
throw new Error("Wasm instance has not been initialised yet.");
|
|
@@ -65,9 +66,20 @@ function getResponseByIdOrEmpty(state, responseId) {
|
|
|
65
66
|
export function createAresAbiImports(state) {
|
|
66
67
|
return {
|
|
67
68
|
env: {
|
|
69
|
+
alloc(size) {
|
|
70
|
+
const instance = getInstanceOrThrow(state);
|
|
71
|
+
const alloc = getAllocOrThrow(instance);
|
|
72
|
+
return alloc(size) >>> 0;
|
|
73
|
+
},
|
|
74
|
+
free_mem(ptr, size) {
|
|
75
|
+
const instance = getInstanceOrThrow(state);
|
|
76
|
+
const free = getFreeOrThrow(instance);
|
|
77
|
+
free(ptr >>> 0, size >>> 0);
|
|
78
|
+
},
|
|
68
79
|
abi_log(messagePtr) {
|
|
69
80
|
const instance = getInstanceOrThrow(state);
|
|
70
|
-
const
|
|
81
|
+
const memory = getMemoryOrThrow(instance);
|
|
82
|
+
const message = readCString(memory, messagePtr);
|
|
71
83
|
if (state.options.onLog) {
|
|
72
84
|
state.options.onLog(message);
|
|
73
85
|
}
|
|
@@ -79,11 +91,14 @@ export function createAresAbiImports(state) {
|
|
|
79
91
|
const instance = getInstanceOrThrow(state);
|
|
80
92
|
const ctx = getRequestContextOrThrow(state);
|
|
81
93
|
const userAgent = ctx.request.headers.get("user-agent") ?? "Cloudflare-Worker";
|
|
82
|
-
|
|
94
|
+
const memory = getMemoryOrThrow(instance);
|
|
95
|
+
const alloc = getAllocOrThrow(instance);
|
|
96
|
+
return writeCString(memory, alloc, userAgent);
|
|
83
97
|
},
|
|
84
98
|
async abi_http_fetch_blocking(requestJsonCstrPtr) {
|
|
85
99
|
const instance = getInstanceOrThrow(state);
|
|
86
|
-
const
|
|
100
|
+
const memory = getMemoryOrThrow(instance);
|
|
101
|
+
const requestJson = readCString(memory, requestJsonCstrPtr);
|
|
87
102
|
const outbound = JSON.parse(requestJson);
|
|
88
103
|
const response = await fetch(outbound.url, {
|
|
89
104
|
method: outbound.method ?? "GET",
|
|
@@ -119,14 +134,16 @@ export function createAresAbiImports(state) {
|
|
|
119
134
|
},
|
|
120
135
|
abi_http_response_copy_body(responseId, outPtr, maxLen) {
|
|
121
136
|
const instance = getInstanceOrThrow(state);
|
|
137
|
+
const memory = getMemoryOrThrow(instance);
|
|
122
138
|
const bodyText = getResponseByIdOrEmpty(state, responseId).bodyText;
|
|
123
|
-
return writeBytesIntoMemory(
|
|
139
|
+
return writeBytesIntoMemory(memory, outPtr, encodeUtf8(bodyText), maxLen);
|
|
124
140
|
},
|
|
125
141
|
abi_http_response_copy_header(responseId, keyCstrPtr, outPtr, maxLen) {
|
|
126
142
|
const instance = getInstanceOrThrow(state);
|
|
127
|
-
const
|
|
143
|
+
const memory = getMemoryOrThrow(instance);
|
|
144
|
+
const key = readCString(memory, keyCstrPtr).toLowerCase();
|
|
128
145
|
const value = getResponseByIdOrEmpty(state, responseId).headers[key] ?? "";
|
|
129
|
-
return writeBytesIntoMemory(
|
|
146
|
+
return writeBytesIntoMemory(memory, outPtr, encodeUtf8(value), maxLen);
|
|
130
147
|
},
|
|
131
148
|
abi_http_response_free(responseId) {
|
|
132
149
|
state.httpResponses.delete(responseId);
|
package/dist/memory.js
CHANGED
|
@@ -26,11 +26,14 @@ export function readCString(memory, ptr, maxLen = 10_000_000) {
|
|
|
26
26
|
}
|
|
27
27
|
export function writeCString(memory, alloc, value) {
|
|
28
28
|
const encoded = encoder.encode(value);
|
|
29
|
-
const ptr = alloc(encoded.length + 1);
|
|
29
|
+
const ptr = alloc(encoded.length + 1) >>> 0;
|
|
30
30
|
if (!ptr) {
|
|
31
31
|
throw new Error(`alloc failed for ${encoded.length + 1} bytes`);
|
|
32
32
|
}
|
|
33
33
|
const bytes = new Uint8Array(memory.buffer);
|
|
34
|
+
if (ptr + encoded.length + 1 > bytes.length) {
|
|
35
|
+
throw new Error("Allocated CString buffer exceeds wasm memory bounds");
|
|
36
|
+
}
|
|
34
37
|
bytes.set(encoded, ptr);
|
|
35
38
|
bytes[ptr + encoded.length] = 0;
|
|
36
39
|
return ptr >>> 0;
|
package/dist/runtime.d.ts
CHANGED
|
@@ -10,3 +10,12 @@ export declare class AresWorkerRuntime<Env = unknown> {
|
|
|
10
10
|
private jsonErrorResponse;
|
|
11
11
|
handleFetch(request: Request, env: Env, ctx: WorkerExecutionContext): Promise<Response>;
|
|
12
12
|
}
|
|
13
|
+
export declare function getMemoryOrThrow(instance: WebAssembly.Instance & {
|
|
14
|
+
exports: Record<string, unknown>;
|
|
15
|
+
}): WebAssembly.Memory;
|
|
16
|
+
export declare function getAllocOrThrow(instance: WebAssembly.Instance & {
|
|
17
|
+
exports: Record<string, unknown>;
|
|
18
|
+
}): (size: number) => number;
|
|
19
|
+
export declare function getFreeOrThrow(instance: WebAssembly.Instance & {
|
|
20
|
+
exports: Record<string, unknown>;
|
|
21
|
+
}): (ptr: number, size?: number) => void;
|
package/dist/runtime.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createAresAbiImports } from "./abi";
|
|
2
2
|
import { headersToObject, readCString, writeJsonCString } from "./memory";
|
|
3
3
|
const DEFAULT_MAX_HTTP_RESPONSE_HANDLES = 32;
|
|
4
|
-
const DEFAULT_MAX_TOTAL_BRIDGE_BYTES = 8 * 1024 * 1024;
|
|
5
|
-
const DEFAULT_MAX_RESPONSE_BODY_BYTES = 512 * 1024;
|
|
4
|
+
const DEFAULT_MAX_TOTAL_BRIDGE_BYTES = 8 * 1024 * 1024;
|
|
5
|
+
const DEFAULT_MAX_RESPONSE_BODY_BYTES = 512 * 1024;
|
|
6
6
|
const DEFAULT_RESPONSE_TTL_MS = 30_000;
|
|
7
7
|
const NOOP_LOGGER = (_) => { };
|
|
8
8
|
function withDefaults(options) {
|
|
@@ -17,6 +17,24 @@ function withDefaults(options) {
|
|
|
17
17
|
responseTtlMs: options.responseTtlMs ?? DEFAULT_RESPONSE_TTL_MS
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
function safeSetU32(memory, ptr, value) {
|
|
21
|
+
if (!ptr)
|
|
22
|
+
return;
|
|
23
|
+
const start = ptr >>> 0;
|
|
24
|
+
if (start + 4 > memory.buffer.byteLength)
|
|
25
|
+
return;
|
|
26
|
+
new DataView(memory.buffer).setUint32(start, value >>> 0, true);
|
|
27
|
+
}
|
|
28
|
+
function safeSetU64Zero(memory, ptr) {
|
|
29
|
+
if (!ptr)
|
|
30
|
+
return;
|
|
31
|
+
const start = ptr >>> 0;
|
|
32
|
+
if (start + 8 > memory.buffer.byteLength)
|
|
33
|
+
return;
|
|
34
|
+
const view = new DataView(memory.buffer);
|
|
35
|
+
view.setUint32(start, 0, true);
|
|
36
|
+
view.setUint32(start + 4, 0, true);
|
|
37
|
+
}
|
|
20
38
|
export class AresWorkerRuntime {
|
|
21
39
|
state;
|
|
22
40
|
constructor(options) {
|
|
@@ -37,7 +55,48 @@ export class AresWorkerRuntime {
|
|
|
37
55
|
return this.state.initPromise;
|
|
38
56
|
}
|
|
39
57
|
this.state.initPromise = (async () => {
|
|
40
|
-
const
|
|
58
|
+
const abiImports = createAresAbiImports(this.state);
|
|
59
|
+
const imports = {
|
|
60
|
+
env: {
|
|
61
|
+
...abiImports.env,
|
|
62
|
+
emscripten_notify_memory_growth: () => { },
|
|
63
|
+
emscripten_date_now: () => Date.now(),
|
|
64
|
+
emscripten_get_now: () => performance.now(),
|
|
65
|
+
abort: (msg, file, line, col) => {
|
|
66
|
+
throw new Error(`abort: ${String(msg)} @ ${String(file)}:${String(line)}:${String(col)}`);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
wasi_snapshot_preview1: {
|
|
70
|
+
fd_write: (_fd, _iovs, _iovsLen, nwritten) => {
|
|
71
|
+
const memory = this.state.instance
|
|
72
|
+
? getMemoryOrThrow(this.state.instance)
|
|
73
|
+
: null;
|
|
74
|
+
if (memory) {
|
|
75
|
+
safeSetU32(memory, nwritten, 0);
|
|
76
|
+
}
|
|
77
|
+
return 0;
|
|
78
|
+
},
|
|
79
|
+
fd_read: (_fd, _iovs, _iovsLen, nread) => {
|
|
80
|
+
const memory = this.state.instance
|
|
81
|
+
? getMemoryOrThrow(this.state.instance)
|
|
82
|
+
: null;
|
|
83
|
+
if (memory) {
|
|
84
|
+
safeSetU32(memory, nread, 0);
|
|
85
|
+
}
|
|
86
|
+
return 0;
|
|
87
|
+
},
|
|
88
|
+
fd_seek: (_fd, _offsetLow, _offsetHigh, _whence, newOffset) => {
|
|
89
|
+
const memory = this.state.instance
|
|
90
|
+
? getMemoryOrThrow(this.state.instance)
|
|
91
|
+
: null;
|
|
92
|
+
if (memory) {
|
|
93
|
+
safeSetU64Zero(memory, newOffset);
|
|
94
|
+
}
|
|
95
|
+
return 0;
|
|
96
|
+
},
|
|
97
|
+
fd_close: (_fd) => 0
|
|
98
|
+
}
|
|
99
|
+
};
|
|
41
100
|
const instantiated = await WebAssembly.instantiate(this.state.options.wasm, imports);
|
|
42
101
|
const instance = instantiated &&
|
|
43
102
|
typeof instantiated === "object" &&
|
|
@@ -45,12 +104,8 @@ export class AresWorkerRuntime {
|
|
|
45
104
|
? instantiated.instance
|
|
46
105
|
: instantiated;
|
|
47
106
|
this.state.instance = instance;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
if (typeof instance.exports.alloc !== "function") {
|
|
52
|
-
throw new Error("Wasm export alloc() was not found.");
|
|
53
|
-
}
|
|
107
|
+
getMemoryOrThrow(instance);
|
|
108
|
+
getAllocOrThrow(instance);
|
|
54
109
|
if (typeof instance.exports.app_init === "function") {
|
|
55
110
|
const rc = await Promise.resolve(instance.exports.app_init());
|
|
56
111
|
if (rc !== 0) {
|
|
@@ -75,8 +130,12 @@ export class AresWorkerRuntime {
|
|
|
75
130
|
freeIfNeeded(ptr, size = 0) {
|
|
76
131
|
if (!ptr)
|
|
77
132
|
return;
|
|
78
|
-
|
|
79
|
-
this.instance
|
|
133
|
+
try {
|
|
134
|
+
const free = getFreeOrThrow(this.instance);
|
|
135
|
+
free(ptr, size);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// ignore cleanup failures
|
|
80
139
|
}
|
|
81
140
|
}
|
|
82
141
|
jsonErrorResponse(message, status = 500) {
|
|
@@ -92,12 +151,15 @@ export class AresWorkerRuntime {
|
|
|
92
151
|
}
|
|
93
152
|
async handleFetch(request, env, ctx) {
|
|
94
153
|
let requestPtr = 0;
|
|
154
|
+
let requestSize = 0;
|
|
95
155
|
let responsePtr = 0;
|
|
96
156
|
await this.init();
|
|
97
157
|
this.setRequestContext({ request, env, ctx });
|
|
98
158
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
159
|
+
const handleHttp = this.instance.exports.handle_http ??
|
|
160
|
+
this.instance.exports.handle_http_json;
|
|
161
|
+
if (typeof handleHttp !== "function") {
|
|
162
|
+
throw new Error("Wasm export handle_http() / handle_http_json() was not found.");
|
|
101
163
|
}
|
|
102
164
|
const requestBody = await request.text();
|
|
103
165
|
const inboundEnvelope = {
|
|
@@ -106,17 +168,21 @@ export class AresWorkerRuntime {
|
|
|
106
168
|
headers: headersToObject(request.headers),
|
|
107
169
|
body: requestBody
|
|
108
170
|
};
|
|
109
|
-
|
|
110
|
-
const
|
|
171
|
+
const memory = getMemoryOrThrow(this.instance);
|
|
172
|
+
const alloc = getAllocOrThrow(this.instance);
|
|
173
|
+
const requestJson = JSON.stringify(inboundEnvelope);
|
|
174
|
+
requestSize = new TextEncoder().encode(requestJson).length + 1;
|
|
175
|
+
requestPtr = writeJsonCString(memory, alloc, inboundEnvelope);
|
|
176
|
+
const responsePtrOrPromise = handleHttp(requestPtr);
|
|
111
177
|
responsePtr = await Promise.resolve(responsePtrOrPromise);
|
|
112
178
|
if (!responsePtr) {
|
|
113
179
|
throw new Error("Wasm returned a null response pointer.");
|
|
114
180
|
}
|
|
115
|
-
const responseJson = readCString(
|
|
181
|
+
const responseJson = readCString(memory, responsePtr);
|
|
116
182
|
const responseEnvelope = JSON.parse(responseJson);
|
|
117
183
|
return new Response(typeof responseEnvelope.body === "string"
|
|
118
184
|
? responseEnvelope.body
|
|
119
|
-
: JSON.stringify(responseEnvelope.body), {
|
|
185
|
+
: JSON.stringify(responseEnvelope.body ?? ""), {
|
|
120
186
|
status: responseEnvelope.status || 200,
|
|
121
187
|
headers: responseEnvelope.headers || {
|
|
122
188
|
"content-type": "application/json; charset=utf-8"
|
|
@@ -128,9 +194,41 @@ export class AresWorkerRuntime {
|
|
|
128
194
|
return this.jsonErrorResponse(message);
|
|
129
195
|
}
|
|
130
196
|
finally {
|
|
131
|
-
this.freeIfNeeded(requestPtr);
|
|
197
|
+
this.freeIfNeeded(requestPtr, requestSize);
|
|
132
198
|
this.freeIfNeeded(responsePtr);
|
|
133
199
|
this.clearRequestContext();
|
|
134
200
|
}
|
|
135
201
|
}
|
|
136
202
|
}
|
|
203
|
+
export function getMemoryOrThrow(instance) {
|
|
204
|
+
const memory = instance.exports.memory;
|
|
205
|
+
if (!(memory instanceof WebAssembly.Memory)) {
|
|
206
|
+
throw new Error("Wasm module does not export memory");
|
|
207
|
+
}
|
|
208
|
+
return memory;
|
|
209
|
+
}
|
|
210
|
+
export function getAllocOrThrow(instance) {
|
|
211
|
+
const candidates = [
|
|
212
|
+
instance.exports.alloc,
|
|
213
|
+
instance.exports._alloc,
|
|
214
|
+
instance.exports.malloc,
|
|
215
|
+
instance.exports._malloc
|
|
216
|
+
];
|
|
217
|
+
for (const fn of candidates) {
|
|
218
|
+
if (typeof fn === "function") {
|
|
219
|
+
return fn;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
throw new Error("Wasm module does not export an allocator (alloc/_alloc/malloc/_malloc)");
|
|
223
|
+
}
|
|
224
|
+
export function getFreeOrThrow(instance) {
|
|
225
|
+
const freeMem = instance.exports.free_mem ?? instance.exports._free_mem;
|
|
226
|
+
if (typeof freeMem === "function") {
|
|
227
|
+
return freeMem;
|
|
228
|
+
}
|
|
229
|
+
const free = instance.exports.free ?? instance.exports._free;
|
|
230
|
+
if (typeof free === "function") {
|
|
231
|
+
return free;
|
|
232
|
+
}
|
|
233
|
+
throw new Error("Wasm module does not export a deallocator (free_mem/_free_mem/free/_free)");
|
|
234
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,9 +4,16 @@ export interface WorkerExecutionContext {
|
|
|
4
4
|
}
|
|
5
5
|
export type WasmExports = {
|
|
6
6
|
memory: WebAssembly.Memory;
|
|
7
|
-
alloc
|
|
7
|
+
alloc?: (size: number) => number;
|
|
8
|
+
_alloc?: (size: number) => number;
|
|
9
|
+
malloc?: (size: number) => number;
|
|
10
|
+
_malloc?: (size: number) => number;
|
|
8
11
|
free_mem?: (ptr: number, size: number) => void;
|
|
12
|
+
_free_mem?: (ptr: number, size: number) => void;
|
|
13
|
+
free?: (ptr: number) => void;
|
|
14
|
+
_free?: (ptr: number) => void;
|
|
9
15
|
app_init?: () => number | Promise<number>;
|
|
16
|
+
handle_http?: (requestJsonPtr: number) => number | Promise<number>;
|
|
10
17
|
handle_http_json?: (requestJsonPtr: number) => number | Promise<number>;
|
|
11
18
|
};
|
|
12
19
|
export type WasmInstanceWithExports = WebAssembly.Instance & {
|
package/package.json
CHANGED