@aresdefencelabs/wasm-http-runtime 0.0.2 → 0.0.4

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { RuntimeState } from "./types";
2
2
  export declare function createAresAbiImports<Env>(state: RuntimeState<Env>): {
3
- ares_abi: {
3
+ env: {
4
+ malloc(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.");
@@ -64,10 +65,21 @@ function getResponseByIdOrEmpty(state, responseId) {
64
65
  }
65
66
  export function createAresAbiImports(state) {
66
67
  return {
67
- ares_abi: {
68
+ env: {
69
+ malloc(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 message = readCString(instance.exports.memory, messagePtr);
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
- return writeCString(instance.exports.memory, instance.exports.alloc, userAgent);
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 requestJson = readCString(instance.exports.memory, requestJsonCstrPtr);
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(instance.exports.memory, outPtr, encodeUtf8(bodyText), maxLen);
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 key = readCString(instance.exports.memory, keyCstrPtr).toLowerCase();
143
+ const memory = getMemoryOrThrow(instance);
144
+ const key = readCString(memory, keyCstrPtr).toLowerCase();
128
145
  const value = getResponseByIdOrEmpty(state, responseId).headers[key] ?? "";
129
- return writeBytesIntoMemory(instance.exports.memory, outPtr, encodeUtf8(value), maxLen);
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; // 8 MB total pool
5
- const DEFAULT_MAX_RESPONSE_BODY_BYTES = 512 * 1024; // 512 KB per response
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 imports = createAresAbiImports(this.state);
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
- if (!(instance.exports.memory instanceof WebAssembly.Memory)) {
49
- throw new Error("Wasm export memory was not found.");
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
- if (typeof this.instance.exports.free_mem === "function") {
79
- this.instance.exports.free_mem(ptr, size);
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
- if (typeof this.instance.exports.handle_http_json !== "function") {
100
- throw new Error("Wasm export handle_http_json() was not found.");
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
- requestPtr = writeJsonCString(this.instance.exports.memory, this.instance.exports.alloc, inboundEnvelope);
110
- const responsePtrOrPromise = this.instance.exports.handle_http_json(requestPtr);
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(this.instance.exports.memory, responsePtr);
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: (size: number) => number;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aresdefencelabs/wasm-http-runtime",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Runtime adapter that connects C++ WebAssembly workers to the Cloudflare Workers runtime via an ABI bridge.",
5
5
  "type": "module",
6
6
  "private": false,