space-data-module-sdk 0.3.1 → 0.5.3
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 +17 -9
- package/package.json +12 -4
- package/src/browser.js +8 -0
- package/src/bundle/constants.js +5 -0
- package/src/bundle/wasm.js +21 -4
- package/src/capabilities.js +46 -0
- package/src/compiler/compileModule.js +346 -17
- package/src/compliance/index.js +4 -2
- package/src/compliance/pluginCompliance.js +4 -47
- package/src/index.d.ts +174 -6
- package/src/index.js +1 -0
- package/src/manifest/browser.js +15 -0
- package/src/transport/index.js +19 -0
- package/src/transport/pki.js +125 -4
- package/src/transport/records.js +493 -0
- package/src/utils/wasmCrypto.js +43 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ This repository is the source of truth for module-level concerns:
|
|
|
12
12
|
- standards-aware compliance and capability validation
|
|
13
13
|
- module compilation and protection
|
|
14
14
|
- the `sds.bundle` single-file custom section
|
|
15
|
-
- deployment authorization
|
|
15
|
+
- deployment authorization plus SDS publication records (`REC`, `PNM`, `ENC`)
|
|
16
16
|
- the first canonical module hostcall/import ABI surface
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
@@ -36,9 +36,11 @@ A module built with this SDK is a `.wasm` artifact with:
|
|
|
36
36
|
- manifest bytes
|
|
37
37
|
- resolved deployment plans and input bindings
|
|
38
38
|
- deployment authorization
|
|
39
|
-
- detached signatures
|
|
40
|
-
- encrypted transport envelopes
|
|
41
39
|
- auxiliary FlatBuffer or raw payloads
|
|
40
|
+
- optional appended SDS `REC` publication trailers carrying:
|
|
41
|
+
- `PNM` digital-signature/publication metadata
|
|
42
|
+
- `ENC` encrypted-delivery metadata
|
|
43
|
+
- auxiliary FlatBuffer or raw payloads
|
|
42
44
|
|
|
43
45
|
The module contract stays the same whether the artifact is loaded directly,
|
|
44
46
|
wrapped in a deployment envelope, or shipped as one bundled `.wasm` file.
|
|
@@ -240,8 +242,11 @@ The full contract split is documented in
|
|
|
240
242
|
## Single-File Bundles
|
|
241
243
|
|
|
242
244
|
`sds.bundle` keeps module delivery to one file without changing WebAssembly
|
|
243
|
-
loadability
|
|
244
|
-
|
|
245
|
+
loadability for the runtime payload itself. The SDK writes the bundle as a
|
|
246
|
+
standard custom section inside the wasm module and, when the artifact is
|
|
247
|
+
signed or encrypted for publication, appends an SDS `REC` trailer after the
|
|
248
|
+
wasm bytes. Loaders must scan and strip that trailer before handing bytes to a
|
|
249
|
+
runtime such as WasmEdge.
|
|
245
250
|
|
|
246
251
|
The reference path lives in
|
|
247
252
|
[`examples/single-file-bundle`](./examples/single-file-bundle):
|
|
@@ -263,8 +268,9 @@ publication descriptor. That descriptor covers:
|
|
|
263
268
|
|
|
264
269
|
- standalone module packages
|
|
265
270
|
- attached module artifacts shipped inside another language library
|
|
266
|
-
- discovery of bundled wasm
|
|
267
|
-
|
|
271
|
+
- discovery of bundled wasm
|
|
272
|
+
- appended `REC` trailers carrying `PNM` and optional `ENC`
|
|
273
|
+
- sidecar `PNM` / `ENC` FlatBuffers when a package does not embed the trailer
|
|
268
274
|
|
|
269
275
|
The full standard is in
|
|
270
276
|
[`docs/module-publication-standard.md`](./docs/module-publication-standard.md),
|
|
@@ -280,8 +286,10 @@ For npm packages, the simplest form is:
|
|
|
280
286
|
}
|
|
281
287
|
```
|
|
282
288
|
|
|
283
|
-
When
|
|
284
|
-
|
|
289
|
+
When publication metadata is published in the same file, it belongs in an
|
|
290
|
+
appended SDS `REC` trailer. Loaders scan from the end of the protected blob,
|
|
291
|
+
resolve `PNM` / `ENC`, strip or decrypt as needed, and only then instantiate
|
|
292
|
+
the remaining raw wasm bytes.
|
|
285
293
|
|
|
286
294
|
## Host ABI
|
|
287
295
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "space-data-module-sdk",
|
|
3
|
-
"version": "0.3
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -12,8 +12,16 @@
|
|
|
12
12
|
"space-data-module": "bin/space-data-module.js"
|
|
13
13
|
},
|
|
14
14
|
"exports": {
|
|
15
|
-
".":
|
|
16
|
-
|
|
15
|
+
".": {
|
|
16
|
+
"browser": "./src/browser.js",
|
|
17
|
+
"default": "./src/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./manifest": {
|
|
20
|
+
"browser": "./src/manifest/browser.js",
|
|
21
|
+
"default": "./src/manifest/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./embedded-manifest": "./src/embeddedManifest.js",
|
|
24
|
+
"./capabilities": "./src/capabilities.js",
|
|
17
25
|
"./compliance": "./src/compliance/index.js",
|
|
18
26
|
"./auth": "./src/auth/index.js",
|
|
19
27
|
"./transport": "./src/transport/index.js",
|
|
@@ -46,7 +54,7 @@
|
|
|
46
54
|
"flatbuffers": "^25.9.23",
|
|
47
55
|
"hd-wallet-wasm": "1.6.0",
|
|
48
56
|
"sdn-emception": "1.0.0",
|
|
49
|
-
"spacedatastandards.org": "
|
|
57
|
+
"spacedatastandards.org": "git+https://github.com/DigitalArsenal/spacedatastandards.org.git#09af351a1"
|
|
50
58
|
},
|
|
51
59
|
"devDependencies": {
|
|
52
60
|
"express": "^4.21.2",
|
package/src/browser.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./manifest/browser.js";
|
|
2
|
+
export * from "./auth/index.js";
|
|
3
|
+
export * from "./transport/index.js";
|
|
4
|
+
export * from "./bundle/index.js";
|
|
5
|
+
export * from "./capabilities.js";
|
|
6
|
+
export * from "./deployment/index.js";
|
|
7
|
+
export * from "./invoke/index.js";
|
|
8
|
+
export * from "./runtime/index.js";
|
package/src/bundle/constants.js
CHANGED
|
@@ -9,3 +9,8 @@ export const SDS_DEPLOYMENT_SECTION_NAME = "sds.deployment";
|
|
|
9
9
|
export const SDS_DEPLOYMENT_ENTRY_ID = "deployment-plan";
|
|
10
10
|
export const SDS_DEPLOYMENT_MEDIA_TYPE =
|
|
11
11
|
"application/vnd.space-data.module.deployment+json";
|
|
12
|
+
export const SDS_GUEST_LINK_OBJECT_ENTRY_ID = "guest-link-object";
|
|
13
|
+
export const SDS_GUEST_LINK_METADATA_ENTRY_ID = "guest-link-metadata";
|
|
14
|
+
export const SDS_GUEST_LINK_SECTION_NAME = "sds.guest-link";
|
|
15
|
+
export const SDS_GUEST_LINK_MEDIA_TYPE =
|
|
16
|
+
"application/vnd.space-data.module.guest-link+json";
|
package/src/bundle/wasm.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
moduleBundleEncodingToName,
|
|
21
21
|
moduleBundleRoleToName,
|
|
22
22
|
} from "./codec.js";
|
|
23
|
+
import { extractPublicationRecordCollection } from "../transport/records.js";
|
|
23
24
|
|
|
24
25
|
const textDecoder = new TextDecoder();
|
|
25
26
|
const textEncoder = new TextEncoder();
|
|
@@ -86,7 +87,11 @@ export function decodeUnsignedLeb128(bytes, offset = 0) {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
export function parseWasmModuleSections(bytes) {
|
|
89
|
-
const
|
|
90
|
+
const protectedArtifact = extractPublicationRecordCollection(bytes);
|
|
91
|
+
const wasmBytes = normalizeBytes(
|
|
92
|
+
protectedArtifact?.payloadBytes ?? bytes,
|
|
93
|
+
"wasm bytes",
|
|
94
|
+
);
|
|
90
95
|
if (wasmBytes.length < 8) {
|
|
91
96
|
throw new Error("WASM module is truncated.");
|
|
92
97
|
}
|
|
@@ -345,10 +350,12 @@ export async function computeCanonicalModuleHash(
|
|
|
345
350
|
bytes,
|
|
346
351
|
options = {},
|
|
347
352
|
) {
|
|
353
|
+
const protectedArtifact = extractPublicationRecordCollection(bytes);
|
|
354
|
+
const candidateBytes = protectedArtifact?.payloadBytes ?? bytes;
|
|
348
355
|
const prefix = String(
|
|
349
356
|
options.customSectionPrefix ?? SDS_CUSTOM_SECTION_PREFIX,
|
|
350
357
|
);
|
|
351
|
-
const canonicalWasmBytes = stripWasmCustomSections(
|
|
358
|
+
const canonicalWasmBytes = stripWasmCustomSections(candidateBytes, (section) =>
|
|
352
359
|
section.name.startsWith(prefix),
|
|
353
360
|
);
|
|
354
361
|
const hashBytes = await sha256Bytes(canonicalWasmBytes);
|
|
@@ -360,7 +367,11 @@ export async function computeCanonicalModuleHash(
|
|
|
360
367
|
}
|
|
361
368
|
|
|
362
369
|
export async function createSingleFileBundle(options = {}) {
|
|
363
|
-
const
|
|
370
|
+
const protectedArtifact = extractPublicationRecordCollection(options.wasmBytes);
|
|
371
|
+
const wasmBytes = normalizeBytes(
|
|
372
|
+
protectedArtifact?.payloadBytes ?? options.wasmBytes,
|
|
373
|
+
"wasmBytes",
|
|
374
|
+
);
|
|
364
375
|
const manifestBytes =
|
|
365
376
|
options.manifestBytes !== undefined
|
|
366
377
|
? normalizeBytes(options.manifestBytes, "manifestBytes")
|
|
@@ -422,7 +433,11 @@ export async function createSingleFileBundle(options = {}) {
|
|
|
422
433
|
}
|
|
423
434
|
|
|
424
435
|
export async function parseSingleFileBundle(bytes, options = {}) {
|
|
425
|
-
const
|
|
436
|
+
const protectedArtifact = extractPublicationRecordCollection(bytes);
|
|
437
|
+
const wasmBytes = normalizeBytes(
|
|
438
|
+
protectedArtifact?.payloadBytes ?? bytes,
|
|
439
|
+
"wasm bytes",
|
|
440
|
+
);
|
|
426
441
|
const customSections = listWasmCustomSections(wasmBytes);
|
|
427
442
|
const bundleSectionName = String(
|
|
428
443
|
options.bundleSectionName ?? SDS_BUNDLE_SECTION_NAME,
|
|
@@ -462,6 +477,8 @@ export async function parseSingleFileBundle(bytes, options = {}) {
|
|
|
462
477
|
) ?? null;
|
|
463
478
|
return {
|
|
464
479
|
wasmBytes,
|
|
480
|
+
protectedArtifactBytes: protectedArtifact?.protectedBytes ?? null,
|
|
481
|
+
publicationRecords: protectedArtifact ?? null,
|
|
465
482
|
bundleBytes,
|
|
466
483
|
bundle,
|
|
467
484
|
entries: parsedEntries,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const RecommendedCapabilityIds = Object.freeze([
|
|
2
|
+
"clock",
|
|
3
|
+
"random",
|
|
4
|
+
"logging",
|
|
5
|
+
"timers",
|
|
6
|
+
"schedule_cron",
|
|
7
|
+
"http",
|
|
8
|
+
"tls",
|
|
9
|
+
"websocket",
|
|
10
|
+
"mqtt",
|
|
11
|
+
"tcp",
|
|
12
|
+
"udp",
|
|
13
|
+
"network",
|
|
14
|
+
"filesystem",
|
|
15
|
+
"pipe",
|
|
16
|
+
"pubsub",
|
|
17
|
+
"protocol_handle",
|
|
18
|
+
"protocol_dial",
|
|
19
|
+
"database",
|
|
20
|
+
"storage_adapter",
|
|
21
|
+
"storage_query",
|
|
22
|
+
"storage_write",
|
|
23
|
+
"context_read",
|
|
24
|
+
"context_write",
|
|
25
|
+
"process_exec",
|
|
26
|
+
"crypto_hash",
|
|
27
|
+
"crypto_sign",
|
|
28
|
+
"crypto_verify",
|
|
29
|
+
"crypto_encrypt",
|
|
30
|
+
"crypto_decrypt",
|
|
31
|
+
"crypto_key_agreement",
|
|
32
|
+
"crypto_kdf",
|
|
33
|
+
"wallet_sign",
|
|
34
|
+
"ipfs",
|
|
35
|
+
"scene_access",
|
|
36
|
+
"entity_access",
|
|
37
|
+
"render_hooks",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export const StandaloneWasiCapabilityIds = Object.freeze([
|
|
41
|
+
"logging",
|
|
42
|
+
"clock",
|
|
43
|
+
"random",
|
|
44
|
+
"filesystem",
|
|
45
|
+
"pipe",
|
|
46
|
+
]);
|
|
@@ -26,10 +26,25 @@ import { runWithEmceptionLock } from "./emceptionNode.js";
|
|
|
26
26
|
import { encodePluginManifest, toEmbeddedPluginManifest } from "../manifest/index.js";
|
|
27
27
|
import { DefaultInvokeExports, InvokeSurface } from "../runtime/constants.js";
|
|
28
28
|
import {
|
|
29
|
-
|
|
29
|
+
appendPublicationRecordCollection,
|
|
30
|
+
createEncryptedEnvelopePayload,
|
|
31
|
+
createPublicationNotice,
|
|
32
|
+
encodePublicationRecordCollection,
|
|
33
|
+
encryptBytesForRecipient,
|
|
34
|
+
extractPublicationRecordCollection,
|
|
30
35
|
generateX25519Keypair,
|
|
31
36
|
} from "../transport/index.js";
|
|
32
37
|
import { createSingleFileBundle } from "../bundle/index.js";
|
|
38
|
+
import {
|
|
39
|
+
SDS_GUEST_LINK_MEDIA_TYPE,
|
|
40
|
+
SDS_GUEST_LINK_METADATA_ENTRY_ID,
|
|
41
|
+
SDS_GUEST_LINK_OBJECT_ENTRY_ID,
|
|
42
|
+
SDS_GUEST_LINK_SECTION_NAME,
|
|
43
|
+
} from "../bundle/constants.js";
|
|
44
|
+
import {
|
|
45
|
+
decodeUnsignedLeb128,
|
|
46
|
+
parseWasmModuleSections,
|
|
47
|
+
} from "../bundle/wasm.js";
|
|
33
48
|
import {
|
|
34
49
|
base64ToBytes,
|
|
35
50
|
bytesToBase64,
|
|
@@ -76,6 +91,201 @@ function buildCompilerArgs(exportedSymbols, options = {}) {
|
|
|
76
91
|
return args;
|
|
77
92
|
}
|
|
78
93
|
|
|
94
|
+
function guestLinkSymbolPrefix(pluginId) {
|
|
95
|
+
const normalized = String(pluginId ?? "module");
|
|
96
|
+
const ascii = Array.from(new TextEncoder().encode(normalized))
|
|
97
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
98
|
+
.join("")
|
|
99
|
+
.slice(0, 24);
|
|
100
|
+
return `sdm_guest_${ascii}_`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseStrongDefinedIdentifiers(nmOutput = "") {
|
|
104
|
+
const identifiers = new Set();
|
|
105
|
+
for (const line of String(nmOutput).split(/\r?\n/)) {
|
|
106
|
+
const match = line.match(/^\S+\s+([A-Za-z])\s+(.+)$/);
|
|
107
|
+
if (!match) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const symbolType = match[1];
|
|
111
|
+
if (!"TDBCGRSV".includes(symbolType)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const demangled = match[2].trim();
|
|
115
|
+
const baseName = demangled
|
|
116
|
+
.replace(/\(.*$/, "")
|
|
117
|
+
.split("::")
|
|
118
|
+
.at(-1)
|
|
119
|
+
?.trim();
|
|
120
|
+
if (!C_IDENTIFIER.test(baseName ?? "")) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
identifiers.add(baseName);
|
|
124
|
+
}
|
|
125
|
+
return Array.from(identifiers).sort();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const WASM_SYM_BINDING_WEAK = 0x01;
|
|
129
|
+
const WASM_SYM_BINDING_LOCAL = 0x02;
|
|
130
|
+
const WASM_SYM_UNDEFINED = 0x10;
|
|
131
|
+
const WASM_SYM_EXPLICIT_NAME = 0x40;
|
|
132
|
+
const WASM_SYMBOL_KIND_FUNCTION = 0;
|
|
133
|
+
const WASM_SYMBOL_KIND_DATA = 1;
|
|
134
|
+
const WASM_SYMBOL_KIND_GLOBAL = 2;
|
|
135
|
+
const WASM_SYMBOL_KIND_SECTION = 3;
|
|
136
|
+
const WASM_SYMBOL_KIND_EVENT = 4;
|
|
137
|
+
const WASM_SYMBOL_KIND_TABLE = 5;
|
|
138
|
+
const LINKING_SYMBOL_TABLE_SUBSECTION_ID = 8;
|
|
139
|
+
const textDecoder = new TextDecoder();
|
|
140
|
+
|
|
141
|
+
function decodeWasmName(bytes, offset) {
|
|
142
|
+
const lengthInfo = decodeUnsignedLeb128(bytes, offset);
|
|
143
|
+
const nameStart = lengthInfo.nextOffset;
|
|
144
|
+
const nameEnd = nameStart + lengthInfo.value;
|
|
145
|
+
if (nameEnd > bytes.length) {
|
|
146
|
+
throw new Error("WASM name extends past end of symbol payload.");
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
value: textDecoder.decode(bytes.subarray(nameStart, nameEnd)),
|
|
150
|
+
nextOffset: nameEnd,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function parseDefinedLinkSymbolsFromObjectBytes(objectBytes) {
|
|
155
|
+
const parsed = parseWasmModuleSections(objectBytes);
|
|
156
|
+
const linkingSection = parsed.sections.find(
|
|
157
|
+
(section) => section.id === 0 && section.name === "linking",
|
|
158
|
+
);
|
|
159
|
+
if (!linkingSection) {
|
|
160
|
+
throw new Error("Guest-link object is missing a linking custom section.");
|
|
161
|
+
}
|
|
162
|
+
const payload = linkingSection.dataBytes;
|
|
163
|
+
let offset = decodeUnsignedLeb128(payload, 0).nextOffset;
|
|
164
|
+
const identifiers = new Set();
|
|
165
|
+
while (offset < payload.length) {
|
|
166
|
+
const subsectionId = payload[offset++];
|
|
167
|
+
const sizeInfo = decodeUnsignedLeb128(payload, offset);
|
|
168
|
+
offset = sizeInfo.nextOffset;
|
|
169
|
+
const subsectionEnd = offset + sizeInfo.value;
|
|
170
|
+
if (subsectionEnd > payload.length) {
|
|
171
|
+
throw new Error("Linking subsection extends past end of payload.");
|
|
172
|
+
}
|
|
173
|
+
if (subsectionId !== LINKING_SYMBOL_TABLE_SUBSECTION_ID) {
|
|
174
|
+
offset = subsectionEnd;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
let cursor = offset;
|
|
178
|
+
const countInfo = decodeUnsignedLeb128(payload, cursor);
|
|
179
|
+
cursor = countInfo.nextOffset;
|
|
180
|
+
for (let symbolIndex = 0; symbolIndex < countInfo.value; symbolIndex += 1) {
|
|
181
|
+
const kind = payload[cursor++];
|
|
182
|
+
const flagsInfo = decodeUnsignedLeb128(payload, cursor);
|
|
183
|
+
const flags = flagsInfo.value;
|
|
184
|
+
cursor = flagsInfo.nextOffset;
|
|
185
|
+
let name = "";
|
|
186
|
+
if (
|
|
187
|
+
kind === WASM_SYMBOL_KIND_FUNCTION ||
|
|
188
|
+
kind === WASM_SYMBOL_KIND_GLOBAL ||
|
|
189
|
+
kind === WASM_SYMBOL_KIND_EVENT ||
|
|
190
|
+
kind === WASM_SYMBOL_KIND_TABLE
|
|
191
|
+
) {
|
|
192
|
+
cursor = decodeUnsignedLeb128(payload, cursor).nextOffset;
|
|
193
|
+
if (
|
|
194
|
+
(flags & WASM_SYM_UNDEFINED) === 0 ||
|
|
195
|
+
(flags & WASM_SYM_EXPLICIT_NAME) !== 0
|
|
196
|
+
) {
|
|
197
|
+
const nameInfo = decodeWasmName(payload, cursor);
|
|
198
|
+
name = nameInfo.value;
|
|
199
|
+
cursor = nameInfo.nextOffset;
|
|
200
|
+
}
|
|
201
|
+
} else if (kind === WASM_SYMBOL_KIND_DATA) {
|
|
202
|
+
const nameInfo = decodeWasmName(payload, cursor);
|
|
203
|
+
name = nameInfo.value;
|
|
204
|
+
cursor = nameInfo.nextOffset;
|
|
205
|
+
if ((flags & WASM_SYM_UNDEFINED) === 0) {
|
|
206
|
+
cursor = decodeUnsignedLeb128(payload, cursor).nextOffset;
|
|
207
|
+
cursor = decodeUnsignedLeb128(payload, cursor).nextOffset;
|
|
208
|
+
cursor = decodeUnsignedLeb128(payload, cursor).nextOffset;
|
|
209
|
+
}
|
|
210
|
+
} else if (kind === WASM_SYMBOL_KIND_SECTION) {
|
|
211
|
+
cursor = decodeUnsignedLeb128(payload, cursor).nextOffset;
|
|
212
|
+
} else {
|
|
213
|
+
throw new Error(`Unsupported WASM linking symbol kind: ${kind}`);
|
|
214
|
+
}
|
|
215
|
+
if ((flags & WASM_SYM_UNDEFINED) !== 0) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if ((flags & WASM_SYM_BINDING_LOCAL) !== 0) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if ((flags & WASM_SYM_BINDING_WEAK) !== 0) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (!C_IDENTIFIER.test(name)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
identifiers.add(name);
|
|
228
|
+
}
|
|
229
|
+
return Array.from(identifiers).sort();
|
|
230
|
+
}
|
|
231
|
+
throw new Error("Guest-link object is missing a symbol table subsection.");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function deriveGuestLinkRenameArgs({
|
|
235
|
+
objectBytes,
|
|
236
|
+
pluginId,
|
|
237
|
+
methodIds = [],
|
|
238
|
+
} = {}) {
|
|
239
|
+
const identifiers = parseDefinedLinkSymbolsFromObjectBytes(objectBytes);
|
|
240
|
+
const prefix = guestLinkSymbolPrefix(pluginId);
|
|
241
|
+
const renamedIdentifiers = Array.from(
|
|
242
|
+
new Set([...identifiers, ...methodIds.filter((value) => C_IDENTIFIER.test(value))]),
|
|
243
|
+
).sort();
|
|
244
|
+
return {
|
|
245
|
+
prefix,
|
|
246
|
+
identifiers: renamedIdentifiers,
|
|
247
|
+
renameArgs: renamedIdentifiers.map(
|
|
248
|
+
(identifier) => `-D${identifier}=${prefix}${identifier}`,
|
|
249
|
+
),
|
|
250
|
+
methodSymbols: Object.fromEntries(
|
|
251
|
+
methodIds.map((methodId) => [methodId, `${prefix}${methodId}`]),
|
|
252
|
+
),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function createGuestLinkBundleEntries(guestLink) {
|
|
257
|
+
if (!guestLink?.objectBytes || guestLink.objectBytes.length === 0) {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
return [
|
|
261
|
+
{
|
|
262
|
+
entryId: SDS_GUEST_LINK_OBJECT_ENTRY_ID,
|
|
263
|
+
role: "auxiliary",
|
|
264
|
+
sectionName: SDS_GUEST_LINK_SECTION_NAME,
|
|
265
|
+
payloadEncoding: "raw-bytes",
|
|
266
|
+
mediaType: "application/wasm",
|
|
267
|
+
payload: guestLink.objectBytes,
|
|
268
|
+
description: "Prefixed guest-link wasm object for monolithic flow linking.",
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
entryId: SDS_GUEST_LINK_METADATA_ENTRY_ID,
|
|
272
|
+
role: "auxiliary",
|
|
273
|
+
sectionName: SDS_GUEST_LINK_SECTION_NAME,
|
|
274
|
+
payloadEncoding: "json-utf8",
|
|
275
|
+
mediaType: SDS_GUEST_LINK_MEDIA_TYPE,
|
|
276
|
+
payload: {
|
|
277
|
+
version: 1,
|
|
278
|
+
format: "wasm-object",
|
|
279
|
+
symbolPrefix: guestLink.symbolPrefix,
|
|
280
|
+
methodSymbols: guestLink.methodSymbols,
|
|
281
|
+
methodIds: Object.keys(guestLink.methodSymbols ?? {}),
|
|
282
|
+
language: guestLink.language,
|
|
283
|
+
},
|
|
284
|
+
description: "Guest-link metadata for monolithic flow linking.",
|
|
285
|
+
},
|
|
286
|
+
];
|
|
287
|
+
}
|
|
288
|
+
|
|
79
289
|
async function getInvokeCppSupportFiles() {
|
|
80
290
|
const [runtimeHeaders, schemaHeaders] = await Promise.all([
|
|
81
291
|
getFlatbuffersCppRuntimeHeaders(),
|
|
@@ -114,6 +324,8 @@ function removeEmceptionDirectory(emception, directoryPath) {
|
|
|
114
324
|
|
|
115
325
|
async function compileWithEmception(options = {}) {
|
|
116
326
|
const {
|
|
327
|
+
manifest,
|
|
328
|
+
language,
|
|
117
329
|
sourceCompilerCommand,
|
|
118
330
|
sourceExtension,
|
|
119
331
|
sourceCode,
|
|
@@ -140,6 +352,7 @@ async function compileWithEmception(options = {}) {
|
|
|
140
352
|
const invokeHeaderPath = path.posix.join(workDir, "space_data_module_invoke.h");
|
|
141
353
|
const invokeSourcePath = path.posix.join(workDir, "plugin-invoke-bridge.cpp");
|
|
142
354
|
const sourceObjectPath = path.posix.join(workDir, "module.o");
|
|
355
|
+
const linkObjectPath = path.posix.join(workDir, "module-link.o");
|
|
143
356
|
const manifestObjectPath = path.posix.join(workDir, "plugin-manifest-exports.o");
|
|
144
357
|
const invokeObjectPath = path.posix.join(workDir, "plugin-invoke-bridge.o");
|
|
145
358
|
const wasmOutputPath = path.posix.join(workDir, "module.wasm");
|
|
@@ -165,6 +378,43 @@ async function compileWithEmception(options = {}) {
|
|
|
165
378
|
"-o",
|
|
166
379
|
sourceObjectPath,
|
|
167
380
|
],
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
for (const command of commands) {
|
|
384
|
+
const result = emception.run(command.join(" "));
|
|
385
|
+
if (result.returncode !== 0) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Compilation failed with ${command[0]} (emception): ${result.stderr || result.stdout}`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const sourceObjectBytes = new Uint8Array(emception.readFile(sourceObjectPath));
|
|
393
|
+
const guestLink = deriveGuestLinkRenameArgs({
|
|
394
|
+
objectBytes: sourceObjectBytes,
|
|
395
|
+
pluginId: manifest?.pluginId,
|
|
396
|
+
methodIds: Array.isArray(manifest?.methods)
|
|
397
|
+
? manifest.methods.map((method) => String(method?.methodId ?? ""))
|
|
398
|
+
: [],
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const linkCompileCommand = [
|
|
402
|
+
sourceCompilerCommand,
|
|
403
|
+
"-c",
|
|
404
|
+
sourcePath,
|
|
405
|
+
`-I${workDir}`,
|
|
406
|
+
...guestLink.renameArgs,
|
|
407
|
+
"-o",
|
|
408
|
+
linkObjectPath,
|
|
409
|
+
];
|
|
410
|
+
const linkCompileResult = emception.run(linkCompileCommand.join(" "));
|
|
411
|
+
if (linkCompileResult.returncode !== 0) {
|
|
412
|
+
throw new Error(
|
|
413
|
+
`Compilation failed with ${linkCompileCommand[0]} (emception): ${linkCompileResult.stderr || linkCompileResult.stdout}`,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const remainingCommands = [
|
|
168
418
|
[
|
|
169
419
|
"em++",
|
|
170
420
|
"-c",
|
|
@@ -195,8 +445,7 @@ async function compileWithEmception(options = {}) {
|
|
|
195
445
|
wasmOutputPath,
|
|
196
446
|
],
|
|
197
447
|
];
|
|
198
|
-
|
|
199
|
-
for (const command of commands) {
|
|
448
|
+
for (const command of remainingCommands) {
|
|
200
449
|
const result = emception.run(command.join(" "));
|
|
201
450
|
if (result.returncode !== 0) {
|
|
202
451
|
throw new Error(
|
|
@@ -206,8 +455,20 @@ async function compileWithEmception(options = {}) {
|
|
|
206
455
|
}
|
|
207
456
|
|
|
208
457
|
const wasmBytes = new Uint8Array(emception.readFile(wasmOutputPath));
|
|
458
|
+
const linkObjectBytes = new Uint8Array(emception.readFile(linkObjectPath));
|
|
209
459
|
await writeFile(resolvedOutputPath, wasmBytes);
|
|
210
|
-
return {
|
|
460
|
+
return {
|
|
461
|
+
wasmBytes,
|
|
462
|
+
outputPath: resolvedOutputPath,
|
|
463
|
+
tempDir,
|
|
464
|
+
guestLink: {
|
|
465
|
+
format: "wasm-object",
|
|
466
|
+
language,
|
|
467
|
+
symbolPrefix: guestLink.prefix,
|
|
468
|
+
methodSymbols: guestLink.methodSymbols,
|
|
469
|
+
objectBytes: linkObjectBytes,
|
|
470
|
+
},
|
|
471
|
+
};
|
|
211
472
|
} finally {
|
|
212
473
|
try {
|
|
213
474
|
removeEmceptionDirectory(emception, workDir);
|
|
@@ -275,6 +536,8 @@ export async function compileModuleFromSource(options = {}) {
|
|
|
275
536
|
noEntry: includeCommandMain !== true,
|
|
276
537
|
};
|
|
277
538
|
const result = await compileWithEmception({
|
|
539
|
+
manifest,
|
|
540
|
+
language: compiler.language,
|
|
278
541
|
sourceCompilerCommand: compiler.command,
|
|
279
542
|
sourceExtension: compiler.extension,
|
|
280
543
|
sourceCode,
|
|
@@ -301,6 +564,7 @@ export async function compileModuleFromSource(options = {}) {
|
|
|
301
564
|
outputPath: resolvedOutputPath,
|
|
302
565
|
tempDir,
|
|
303
566
|
wasmBytes,
|
|
567
|
+
guestLink: result.guestLink,
|
|
304
568
|
manifestWarnings: warnings,
|
|
305
569
|
report,
|
|
306
570
|
};
|
|
@@ -383,35 +647,100 @@ export async function protectModuleArtifact(options = {}) {
|
|
|
383
647
|
authorization: signedAuthorization,
|
|
384
648
|
};
|
|
385
649
|
|
|
386
|
-
let encryptedEnvelope = null;
|
|
387
|
-
if (options.recipientPublicKeyHex) {
|
|
388
|
-
encryptedEnvelope = await encryptJsonForRecipient({
|
|
389
|
-
payload,
|
|
390
|
-
recipientPublicKey: hexToBytes(options.recipientPublicKeyHex),
|
|
391
|
-
context: "space-data-module-sdk/package",
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
650
|
let singleFileBundle = null;
|
|
396
651
|
if (options.singleFileBundle === true) {
|
|
652
|
+
const additionalEntries = [
|
|
653
|
+
...(Array.isArray(options.bundleEntries) ? options.bundleEntries : []),
|
|
654
|
+
...createGuestLinkBundleEntries(options.guestLink),
|
|
655
|
+
];
|
|
397
656
|
singleFileBundle = await createSingleFileBundle({
|
|
398
657
|
wasmBytes,
|
|
399
658
|
manifest,
|
|
400
659
|
authorization: signedAuthorization,
|
|
401
|
-
|
|
402
|
-
entries: options.bundleEntries,
|
|
660
|
+
entries: additionalEntries,
|
|
403
661
|
});
|
|
404
662
|
}
|
|
405
663
|
|
|
664
|
+
const bundleBytes = singleFileBundle?.wasmBytes ?? wasmBytes;
|
|
665
|
+
let publicationNotice = null;
|
|
666
|
+
let publicationRecordsBytes = null;
|
|
667
|
+
let protectedArtifactBytes = null;
|
|
668
|
+
let encryptedEnvelope = null;
|
|
669
|
+
|
|
670
|
+
if (options.recipientPublicKeyHex) {
|
|
671
|
+
const encryptedBase = await encryptBytesForRecipient({
|
|
672
|
+
plaintext: bundleBytes,
|
|
673
|
+
recipientPublicKey: hexToBytes(options.recipientPublicKeyHex),
|
|
674
|
+
context: "space-data-module-sdk/package",
|
|
675
|
+
rootType: "WASM",
|
|
676
|
+
});
|
|
677
|
+
const encryptedBaseBytes = base64ToBytes(encryptedBase.protectedBlobBase64);
|
|
678
|
+
const parsedEncryptedBase = extractPublicationRecordCollection(encryptedBaseBytes);
|
|
679
|
+
publicationNotice = await createPublicationNotice({
|
|
680
|
+
payloadBytes: parsedEncryptedBase.payloadBytes,
|
|
681
|
+
artifactId,
|
|
682
|
+
programId,
|
|
683
|
+
fileName: `${artifactId}.wasm`,
|
|
684
|
+
fileId: programId,
|
|
685
|
+
signer,
|
|
686
|
+
});
|
|
687
|
+
publicationRecordsBytes = encodePublicationRecordCollection({
|
|
688
|
+
enc: parsedEncryptedBase.enc,
|
|
689
|
+
pnm: publicationNotice,
|
|
690
|
+
});
|
|
691
|
+
protectedArtifactBytes = appendPublicationRecordCollection(
|
|
692
|
+
parsedEncryptedBase.payloadBytes,
|
|
693
|
+
publicationRecordsBytes,
|
|
694
|
+
);
|
|
695
|
+
encryptedEnvelope = createEncryptedEnvelopePayload({
|
|
696
|
+
protectedBlobBytes: protectedArtifactBytes,
|
|
697
|
+
parsedProtectedBlob: {
|
|
698
|
+
payloadBytes: parsedEncryptedBase.payloadBytes,
|
|
699
|
+
recordCollectionBytes: publicationRecordsBytes,
|
|
700
|
+
enc: parsedEncryptedBase.enc,
|
|
701
|
+
pnm: publicationNotice,
|
|
702
|
+
},
|
|
703
|
+
enc: parsedEncryptedBase.enc,
|
|
704
|
+
context: parsedEncryptedBase.enc?.context,
|
|
705
|
+
});
|
|
706
|
+
} else {
|
|
707
|
+
publicationNotice = await createPublicationNotice({
|
|
708
|
+
payloadBytes: bundleBytes,
|
|
709
|
+
artifactId,
|
|
710
|
+
programId,
|
|
711
|
+
fileName: `${artifactId}.wasm`,
|
|
712
|
+
fileId: programId,
|
|
713
|
+
signer,
|
|
714
|
+
});
|
|
715
|
+
publicationRecordsBytes = encodePublicationRecordCollection({
|
|
716
|
+
pnm: publicationNotice,
|
|
717
|
+
});
|
|
718
|
+
protectedArtifactBytes = appendPublicationRecordCollection(
|
|
719
|
+
bundleBytes,
|
|
720
|
+
publicationRecordsBytes,
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (singleFileBundle) {
|
|
725
|
+
singleFileBundle = {
|
|
726
|
+
...singleFileBundle,
|
|
727
|
+
wasmBytes: protectedArtifactBytes,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
406
731
|
return {
|
|
407
732
|
mnemonic: identity.mnemonic,
|
|
408
733
|
signingPublicKeyHex: bytesToHex(identity.signingKey.publicKey),
|
|
409
734
|
signingPath: identity.signingKey.path,
|
|
410
735
|
payload,
|
|
411
|
-
|
|
736
|
+
publicationNotice,
|
|
737
|
+
publicationRecordsBytes,
|
|
738
|
+
protectedArtifactBytes,
|
|
739
|
+
protectedArtifactBase64: bytesToBase64(protectedArtifactBytes),
|
|
740
|
+
encrypted: Boolean(options.recipientPublicKeyHex),
|
|
412
741
|
encryptedEnvelope,
|
|
413
742
|
singleFileBundle,
|
|
414
|
-
bundledWasmBytes: singleFileBundle?.wasmBytes ??
|
|
743
|
+
bundledWasmBytes: singleFileBundle?.wasmBytes ?? protectedArtifactBytes,
|
|
415
744
|
};
|
|
416
745
|
}
|
|
417
746
|
|