space-data-module-sdk 0.8.0 → 0.8.2
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 -3
- package/bin/space-data-module.js +8 -6
- package/package.json +2 -2
- package/schemas/spacedatastandards/PLG.fbs +191 -0
- package/src/bundle/wasm.js +31 -9
- package/src/compliance/index.js +4 -0
- package/src/compliance/pluginCompliance.js +158 -2
- package/src/embeddedManifest.js +25 -5
- package/src/generated/orbpro/invoke/plugin-invoke-request.js +2 -2
- package/src/generated/orbpro/invoke/plugin-invoke-response.js +2 -2
- package/src/generated/orbpro/stream/flat-buffer-type-ref.js +2 -2
- package/src/generated/orbpro/stream/typed-arena-buffer.js +2 -2
- package/src/generated/spacedatastandards/plg/entry-function.js +86 -0
- package/src/generated/spacedatastandards/plg/entry-function.ts +119 -0
- package/src/generated/spacedatastandards/plg/index.js +16 -0
- package/src/generated/spacedatastandards/plg/index.ts +11 -0
- package/src/generated/spacedatastandards/plg/plg.js +657 -0
- package/src/generated/spacedatastandards/plg/plg.ts +924 -0
- package/src/generated/spacedatastandards/plg/plugin-capability.js +66 -0
- package/src/generated/spacedatastandards/plg/plugin-capability.ts +84 -0
- package/src/generated/spacedatastandards/plg/plugin-category.js +15 -0
- package/src/generated/spacedatastandards/plg/plugin-category.ts +53 -0
- package/src/generated/spacedatastandards/plg/plugin-dependency.js +63 -0
- package/src/generated/spacedatastandards/plg/plugin-dependency.ts +86 -0
- package/src/generated/spacedatastandards/plg/publication-state.js +9 -0
- package/src/generated/spacedatastandards/plg/publication-state.ts +23 -0
- package/src/generated/spacedatastandards/plg/purchase-tier.js +9 -0
- package/src/generated/spacedatastandards/plg/purchase-tier.ts +23 -0
- package/src/invoke/codec.js +1 -1
- package/src/manifest/browser.js +7 -0
- package/src/manifest/codec.js +73 -0
- package/src/manifest/index.js +7 -0
- package/src/manifest/legacyToPlg.js +249 -0
- package/src/manifest/plgCodec.js +826 -0
- package/src/standards/sharedCatalog.js +132 -0
- package/src/testing/native/wasmedge_emscripten_pthread_runner.c +17 -3
- package/src/vendor/flatbuffers/LICENSE +202 -0
- package/src/vendor/flatbuffers/builder.js +534 -0
- package/src/vendor/flatbuffers/byte-buffer.js +253 -0
- package/src/vendor/flatbuffers/constants.js +4 -0
- package/src/vendor/flatbuffers/encoding.js +5 -0
- package/src/vendor/flatbuffers/flatbuffers.js +5 -0
- package/src/vendor/flatbuffers/utils.js +4 -0
package/README.md
CHANGED
|
@@ -410,11 +410,22 @@ for resolved protocol installations and producer input bindings.
|
|
|
410
410
|
## Publication Protection Extensions
|
|
411
411
|
|
|
412
412
|
Digital signature and encrypted-delivery metadata are publication-layer
|
|
413
|
-
extensions
|
|
413
|
+
extensions. The canonical runtime module is still:
|
|
414
414
|
|
|
415
415
|
- valid `.wasm`
|
|
416
416
|
- optionally carrying one appended SDS `REC` trailer after the wasm bytes
|
|
417
417
|
|
|
418
|
+
The same-file protected delivery layout is:
|
|
419
|
+
|
|
420
|
+
```text
|
|
421
|
+
protected-payload-bytes || REC-flatbuffer-bytes || uint32le(REC length) || "$REC"
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
For signed-only delivery, the protected payload bytes are the wasm bytes. For
|
|
425
|
+
encrypted binary delivery, the protected payload bytes are ciphertext and the
|
|
426
|
+
appended `REC` trailer carries `ENC` so a loader can decrypt them back to the
|
|
427
|
+
canonical wasm before runtime startup.
|
|
428
|
+
|
|
418
429
|
That trailing `REC` FlatBuffer is the standards-backed container for
|
|
419
430
|
publication records:
|
|
420
431
|
|
|
@@ -435,7 +446,7 @@ The loader contract is always:
|
|
|
435
446
|
FlatBuffers from `spacedatastandards.org`.
|
|
436
447
|
4. Resolve `PNM` for signature/publication metadata.
|
|
437
448
|
5. Resolve `ENC` if the delivery was encrypted.
|
|
438
|
-
6. Strip the trailer or decrypt the protected payload.
|
|
449
|
+
6. Strip the trailer or decrypt the protected payload bytes.
|
|
439
450
|
7. Hand the remaining raw wasm bytes to the runtime.
|
|
440
451
|
|
|
441
452
|
Aligned-binary payloads are a separate invoke-ABI optimization. They do not
|
|
@@ -547,9 +558,12 @@ npx space-data-module check --manifest ./manifest.json --wasm ./dist/isomorphic/
|
|
|
547
558
|
# Compile C/C++ source and embed the manifest
|
|
548
559
|
npx space-data-module compile --manifest ./manifest.json --source ./src/module.c --out ./dist/isomorphic/module.wasm
|
|
549
560
|
|
|
550
|
-
# Sign
|
|
561
|
+
# Sign a deployment payload and print JSON metadata
|
|
551
562
|
npx space-data-module protect --manifest ./manifest.json --wasm ./dist/isomorphic/module.wasm --json
|
|
552
563
|
|
|
564
|
+
# Emit an encrypted binary with an appended REC trailer
|
|
565
|
+
npx space-data-module protect --manifest ./manifest.json --wasm ./dist/isomorphic/module.wasm --recipient-public-key <hex> --out ./dist/module.wasm.enc
|
|
566
|
+
|
|
553
567
|
# Emit a single-file bundled wasm
|
|
554
568
|
npx space-data-module protect --manifest ./manifest.json --wasm ./dist/isomorphic/module.wasm --single-file-bundle --out ./dist/module.bundle.wasm
|
|
555
569
|
```
|
package/bin/space-data-module.js
CHANGED
|
@@ -106,6 +106,7 @@ function printUsage() {
|
|
|
106
106
|
space-data-module check --manifest ./manifest.json --wasm ./dist/module.wasm
|
|
107
107
|
space-data-module compile --manifest ./manifest.json --source ./src/module.c --out ./dist/module.wasm
|
|
108
108
|
space-data-module protect --manifest ./manifest.json --wasm ./dist/module.wasm --json
|
|
109
|
+
space-data-module protect --manifest ./manifest.json --wasm ./dist/module.wasm --recipient-public-key <hex> --out ./dist/module.wasm.enc
|
|
109
110
|
space-data-module protect --manifest ./manifest.json --wasm ./dist/module.wasm --single-file-bundle --out ./dist/module.bundle.wasm
|
|
110
111
|
`);
|
|
111
112
|
}
|
|
@@ -213,9 +214,6 @@ async function runProtect(argv) {
|
|
|
213
214
|
if (!options.manifestPath || !options.wasmPath) {
|
|
214
215
|
throw new Error("protect requires --manifest and --wasm.");
|
|
215
216
|
}
|
|
216
|
-
if (options.outputPath && options.singleFileBundle !== true) {
|
|
217
|
-
throw new Error("protect only supports --out together with --single-file-bundle.");
|
|
218
|
-
}
|
|
219
217
|
const manifest = await loadManifestFromFile(options.manifestPath);
|
|
220
218
|
const wasmBytes = new Uint8Array(await readFile(options.wasmPath));
|
|
221
219
|
const result = await protectModuleArtifact({
|
|
@@ -225,8 +223,11 @@ async function runProtect(argv) {
|
|
|
225
223
|
mnemonic: options.mnemonic,
|
|
226
224
|
singleFileBundle: options.singleFileBundle,
|
|
227
225
|
});
|
|
228
|
-
if (options.
|
|
229
|
-
await writeFile(
|
|
226
|
+
if (options.outputPath) {
|
|
227
|
+
await writeFile(
|
|
228
|
+
options.outputPath,
|
|
229
|
+
result.bundledWasmBytes ?? result.protectedArtifactBytes,
|
|
230
|
+
);
|
|
230
231
|
}
|
|
231
232
|
if (options.json) {
|
|
232
233
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -235,7 +236,8 @@ async function runProtect(argv) {
|
|
|
235
236
|
console.log(`signingPublicKeyHex=${result.signingPublicKeyHex}`);
|
|
236
237
|
console.log(`encrypted=${result.encrypted}`);
|
|
237
238
|
console.log(`wasmBase64Length=${bytesToBase64(wasmBytes).length}`);
|
|
238
|
-
|
|
239
|
+
console.log(`protectedArtifactBytes=${result.protectedArtifactBytes.length}`);
|
|
240
|
+
if (options.singleFileBundle && result.bundledWasmBytes) {
|
|
239
241
|
console.log(`singleFileBundle=true`);
|
|
240
242
|
console.log(`bundledWasmBytes=${result.bundledWasmBytes.length}`);
|
|
241
243
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "space-data-module-sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
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",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"flatsql": "^0.4.0",
|
|
73
73
|
"hd-wallet-wasm": "1.6.0",
|
|
74
74
|
"sdn-emception": "1.0.0",
|
|
75
|
-
"spacedatastandards.org": "1.
|
|
75
|
+
"spacedatastandards.org": "^1.92.0"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"express": "^4.21.2"
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Canonical plugin manifest schema.
|
|
2
|
+
//
|
|
3
|
+
// Upstream source: https://github.com/DigitalArsenal/spacedatastandards.org
|
|
4
|
+
// Path: schema/PLG/main.fbs
|
|
5
|
+
// Version: 1.0.6
|
|
6
|
+
// Upstream hash: 2c9ca90ec923b2476832517dee715559c45dd198d3bdd4b5b3dfe0a4d05c4882
|
|
7
|
+
//
|
|
8
|
+
// This is the canonical spacedatastandards.org "PLG" manifest.
|
|
9
|
+
// It is vendored here verbatim so the plugin SDK can depend on a stable copy
|
|
10
|
+
// without adding a network or submodule fetch at build time. Keep this file
|
|
11
|
+
// byte-for-byte identical to the upstream version; if a refresh is needed,
|
|
12
|
+
// re-vendor from the upstream path above rather than hand-editing.
|
|
13
|
+
|
|
14
|
+
// Hash: 2c9ca90ec923b2476832517dee715559c45dd198d3bdd4b5b3dfe0a4d05c4882
|
|
15
|
+
// Version: 1.0.6
|
|
16
|
+
// -----------------------------------END_HEADER
|
|
17
|
+
/// Plugin type category
|
|
18
|
+
enum pluginCategory : byte {
|
|
19
|
+
/// Sensor simulation and analysis
|
|
20
|
+
Sensor,
|
|
21
|
+
/// Orbital propagation algorithms
|
|
22
|
+
Propagator,
|
|
23
|
+
/// Custom rendering/visualization
|
|
24
|
+
Renderer,
|
|
25
|
+
/// Data analysis and processing
|
|
26
|
+
Analysis,
|
|
27
|
+
/// External data source integration
|
|
28
|
+
DataSource,
|
|
29
|
+
/// Electronic warfare simulation
|
|
30
|
+
EW,
|
|
31
|
+
/// Communications modeling
|
|
32
|
+
Comms,
|
|
33
|
+
/// Physics simulation
|
|
34
|
+
Physics,
|
|
35
|
+
/// GLSL shader plugins for custom visualization
|
|
36
|
+
Shader
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Storefront payment model for the plugin listing
|
|
40
|
+
enum purchaseTier : byte {
|
|
41
|
+
/// No payment required
|
|
42
|
+
Free,
|
|
43
|
+
/// Single one-time purchase
|
|
44
|
+
OneTime,
|
|
45
|
+
/// Recurring subscription purchase
|
|
46
|
+
Subscription
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Publication visibility for the plugin listing
|
|
50
|
+
enum publicationState : byte {
|
|
51
|
+
/// Discoverable in public storefront listings
|
|
52
|
+
Public,
|
|
53
|
+
/// Addressable directly but hidden from public browse surfaces
|
|
54
|
+
Unlisted,
|
|
55
|
+
/// No longer offered for new installs or purchases
|
|
56
|
+
Retired
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Plugin capability declaration
|
|
60
|
+
table PluginCapability {
|
|
61
|
+
/// Capability name, e.g., "gpu_compute", "wasm_simd"
|
|
62
|
+
NAME: string;
|
|
63
|
+
/// Capability version
|
|
64
|
+
VERSION: string;
|
|
65
|
+
/// Whether this capability is required
|
|
66
|
+
REQUIRED: bool;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Plugin dependency on another plugin
|
|
70
|
+
table PluginDependency {
|
|
71
|
+
/// Plugin ID of the dependency
|
|
72
|
+
PLUGIN_ID: string;
|
|
73
|
+
/// Minimum version required (semver)
|
|
74
|
+
MIN_VERSION: string;
|
|
75
|
+
/// Maximum version allowed (optional)
|
|
76
|
+
MAX_VERSION: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Plugin entry point function definition
|
|
80
|
+
table EntryFunction {
|
|
81
|
+
/// Function name as exported from WASM
|
|
82
|
+
NAME: string (required);
|
|
83
|
+
/// Human-readable description
|
|
84
|
+
DESCRIPTION: string;
|
|
85
|
+
/// Input parameter types (FlatBuffer schema names)
|
|
86
|
+
INPUT_SCHEMAS: [string];
|
|
87
|
+
/// Output type (FlatBuffer schema name)
|
|
88
|
+
OUTPUT_SCHEMA: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Plugin Manifest - canonical signed storefront and WASM distribution record
|
|
92
|
+
table PLG {
|
|
93
|
+
/// Unique identifier for the plugin
|
|
94
|
+
PLUGIN_ID: string (required);
|
|
95
|
+
/// Human-readable plugin name
|
|
96
|
+
NAME: string (required);
|
|
97
|
+
/// Plugin version (semver format)
|
|
98
|
+
VERSION: string (required);
|
|
99
|
+
/// Detailed description of plugin functionality
|
|
100
|
+
DESCRIPTION: string;
|
|
101
|
+
/// Short marketing summary shown in storefront listings
|
|
102
|
+
TAGLINE: string;
|
|
103
|
+
/// Type/category of the plugin
|
|
104
|
+
PLUGIN_TYPE: pluginCategory;
|
|
105
|
+
/// Human-readable publisher or organization name
|
|
106
|
+
PUBLISHER_NAME: string;
|
|
107
|
+
/// Publisher handle or username
|
|
108
|
+
PUBLISHER_HANDLE: string;
|
|
109
|
+
/// Canonical publisher website
|
|
110
|
+
PUBLISHER_URL: string;
|
|
111
|
+
/// Support or helpdesk URL for this plugin
|
|
112
|
+
SUPPORT_URL: string;
|
|
113
|
+
/// Search and categorization tags for discovery
|
|
114
|
+
TAGS: [string];
|
|
115
|
+
/// Short feature bullets highlighted in storefront listings
|
|
116
|
+
FEATURES: [string];
|
|
117
|
+
/// Screenshot URLs showing the plugin in use
|
|
118
|
+
SCREENSHOT_URLS: [string];
|
|
119
|
+
/// Optional hero/banner image URL for the listing
|
|
120
|
+
BANNER_URL: string;
|
|
121
|
+
|
|
122
|
+
/// ABI version for compatibility checking
|
|
123
|
+
ABI_VERSION: uint32 = 1;
|
|
124
|
+
|
|
125
|
+
/// SHA256 hash of the decrypted WASM binary
|
|
126
|
+
WASM_HASH: [ubyte];
|
|
127
|
+
/// Size of decrypted WASM binary in bytes
|
|
128
|
+
WASM_SIZE: uint64;
|
|
129
|
+
/// IPFS CID of the encrypted WASM binary
|
|
130
|
+
WASM_CID: string;
|
|
131
|
+
/// SHA256 hash of the encrypted delivery artifact bytes
|
|
132
|
+
ENCRYPTED_WASM_HASH: [ubyte];
|
|
133
|
+
/// Size of the encrypted delivery artifact in bytes
|
|
134
|
+
ENCRYPTED_WASM_SIZE: uint64;
|
|
135
|
+
|
|
136
|
+
/// Entry point functions exported by the plugin
|
|
137
|
+
ENTRY_FUNCTIONS: [EntryFunction];
|
|
138
|
+
/// FlatBuffer schemas required by this plugin
|
|
139
|
+
REQUIRED_SCHEMAS: [string];
|
|
140
|
+
/// Other plugins this depends on
|
|
141
|
+
DEPENDENCIES: [PluginDependency];
|
|
142
|
+
/// Capabilities provided by this plugin
|
|
143
|
+
CAPABILITIES: [PluginCapability];
|
|
144
|
+
|
|
145
|
+
/// Peer ID of the plugin provider
|
|
146
|
+
PROVIDER_PEER_ID: string;
|
|
147
|
+
/// IPFS CID of provider's EPM (Entity Profile Message)
|
|
148
|
+
PROVIDER_EPM_CID: string;
|
|
149
|
+
|
|
150
|
+
/// Whether the WASM binary is encrypted
|
|
151
|
+
ENCRYPTED: bool = true;
|
|
152
|
+
/// Canonical required scope for grant issuance
|
|
153
|
+
REQUIRED_SCOPE: string;
|
|
154
|
+
/// Provider-local identifier for the module content key
|
|
155
|
+
KEY_ID: string;
|
|
156
|
+
/// Allowed requester domains for module grants
|
|
157
|
+
ALLOWED_DOMAINS: [string];
|
|
158
|
+
/// Maximum grant timeout allowed for this module publication
|
|
159
|
+
MAX_GRANT_TIMEOUT_MS: uint64;
|
|
160
|
+
/// Minimum permissions required to run
|
|
161
|
+
MIN_PERMISSIONS: [string];
|
|
162
|
+
|
|
163
|
+
/// Unix timestamp when plugin was created
|
|
164
|
+
CREATED_AT: uint64;
|
|
165
|
+
/// Unix timestamp when plugin was last updated
|
|
166
|
+
UPDATED_AT: uint64;
|
|
167
|
+
/// URL to plugin documentation
|
|
168
|
+
DOCUMENTATION_URL: string;
|
|
169
|
+
/// URL to plugin changelog or release notes
|
|
170
|
+
CHANGELOG_URL: string;
|
|
171
|
+
/// URL to plugin icon/logo
|
|
172
|
+
ICON_URL: string;
|
|
173
|
+
/// License identifier (SPDX format)
|
|
174
|
+
LICENSE: string;
|
|
175
|
+
/// Commercial model used for storefront purchase flows
|
|
176
|
+
PAYMENT_MODEL: purchaseTier = Free;
|
|
177
|
+
/// Price in USD cents for one-time purchase or subscription period
|
|
178
|
+
PRICE_USD_CENTS: uint32;
|
|
179
|
+
/// Subscription billing period length in days
|
|
180
|
+
SUBSCRIPTION_PERIOD_DAYS: uint32;
|
|
181
|
+
/// Accepted payment methods, e.g. "stripe", "sol", "usdc"
|
|
182
|
+
ACCEPTED_PAYMENT_METHODS: [string];
|
|
183
|
+
/// Storefront publication state for this manifest version
|
|
184
|
+
LISTING_STATUS: publicationState = Public;
|
|
185
|
+
|
|
186
|
+
/// Ed25519 signature from provider over manifest
|
|
187
|
+
SIGNATURE: [ubyte];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
root_type PLG;
|
|
191
|
+
file_identifier "$PLG";
|
package/src/bundle/wasm.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { canonicalBytes } from "../auth/canonicalize.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
decodePluginManifest,
|
|
4
|
+
decodePlgManifest,
|
|
5
|
+
encodePlgManifest,
|
|
6
|
+
isPlgManifestBuffer,
|
|
7
|
+
legacyManifestToPlg,
|
|
8
|
+
} from "../manifest/index.js";
|
|
3
9
|
import { toUint8Array } from "../runtime/bufferLike.js";
|
|
4
10
|
import { sha256Bytes } from "../utils/crypto.js";
|
|
5
11
|
import { bytesToHex } from "../utils/encoding.js";
|
|
@@ -226,20 +232,36 @@ function normalizeJsonPayload(value) {
|
|
|
226
232
|
return new Uint8Array(canonicalBytes(value));
|
|
227
233
|
}
|
|
228
234
|
|
|
235
|
+
function encodeBundleManifest(manifest) {
|
|
236
|
+
return encodePlgManifest(legacyManifestToPlg(manifest));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function decodeBundleManifest(payloadBytes) {
|
|
240
|
+
return isPlgManifestBuffer(payloadBytes)
|
|
241
|
+
? decodePlgManifest(payloadBytes)
|
|
242
|
+
: decodePluginManifest(payloadBytes);
|
|
243
|
+
}
|
|
244
|
+
|
|
229
245
|
function normalizeManifestEntry(manifest, manifestBytes) {
|
|
230
246
|
if (!manifest && !manifestBytes) {
|
|
231
247
|
return null;
|
|
232
248
|
}
|
|
233
|
-
const payload = manifestBytes ??
|
|
249
|
+
const payload = manifestBytes ?? encodeBundleManifest(manifest);
|
|
250
|
+
const isPlg = isPlgManifestBuffer(payload);
|
|
234
251
|
return {
|
|
235
252
|
entryId: "manifest",
|
|
236
253
|
role: "manifest",
|
|
237
254
|
sectionName: "sds.manifest",
|
|
238
255
|
payloadEncoding: "flatbuffer",
|
|
239
|
-
typeRef:
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
256
|
+
typeRef: isPlg
|
|
257
|
+
? {
|
|
258
|
+
schemaName: "PLG.fbs",
|
|
259
|
+
fileIdentifier: "$PLG",
|
|
260
|
+
}
|
|
261
|
+
: {
|
|
262
|
+
schemaName: "PluginManifest.fbs",
|
|
263
|
+
fileIdentifier: "PMAN",
|
|
264
|
+
},
|
|
243
265
|
payload,
|
|
244
266
|
description: "Canonical plugin manifest.",
|
|
245
267
|
};
|
|
@@ -330,7 +352,7 @@ function buildParsedEntries(bundle) {
|
|
|
330
352
|
parsedEntry.payloadEncodingName === "flatbuffer"
|
|
331
353
|
) {
|
|
332
354
|
try {
|
|
333
|
-
parsedEntry.decodedManifest =
|
|
355
|
+
parsedEntry.decodedManifest = decodeBundleManifest(payloadBytes);
|
|
334
356
|
} catch {
|
|
335
357
|
parsedEntry.decodedManifest = null;
|
|
336
358
|
}
|
|
@@ -380,7 +402,7 @@ export async function createSingleFileBundle(options = {}) {
|
|
|
380
402
|
options.manifestBytes !== undefined
|
|
381
403
|
? normalizeBytes(options.manifestBytes, "manifestBytes")
|
|
382
404
|
: options.manifest
|
|
383
|
-
?
|
|
405
|
+
? encodeBundleManifest(options.manifest)
|
|
384
406
|
: null;
|
|
385
407
|
const manifestEntry = normalizeManifestEntry(options.manifest, manifestBytes);
|
|
386
408
|
const rawEntries = [
|
|
@@ -463,7 +485,7 @@ export async function parseSingleFileBundle(bytes, options = {}) {
|
|
|
463
485
|
let manifest = null;
|
|
464
486
|
if (manifestEntry) {
|
|
465
487
|
try {
|
|
466
|
-
manifest =
|
|
488
|
+
manifest = decodeBundleManifest(new Uint8Array(manifestEntry.payload ?? []));
|
|
467
489
|
} catch {
|
|
468
490
|
manifest = null;
|
|
469
491
|
}
|
package/src/compliance/index.js
CHANGED
|
@@ -2,8 +2,10 @@ import {
|
|
|
2
2
|
findManifestFiles,
|
|
3
3
|
getWasmExportNames,
|
|
4
4
|
getWasmExportNamesFromFile,
|
|
5
|
+
hasLegacyPmanManifest,
|
|
5
6
|
loadManifestFromFile,
|
|
6
7
|
loadComplianceConfig,
|
|
8
|
+
locateEmbeddedPlgManifest,
|
|
7
9
|
resolveManifestFiles,
|
|
8
10
|
validatePluginArtifact,
|
|
9
11
|
validatePluginManifest,
|
|
@@ -49,8 +51,10 @@ export {
|
|
|
49
51
|
findManifestFiles,
|
|
50
52
|
getWasmExportNames,
|
|
51
53
|
getWasmExportNamesFromFile,
|
|
54
|
+
hasLegacyPmanManifest,
|
|
52
55
|
loadManifestFromFile,
|
|
53
56
|
loadComplianceConfig,
|
|
57
|
+
locateEmbeddedPlgManifest,
|
|
54
58
|
RecommendedCapabilityIds,
|
|
55
59
|
StandaloneWasiCapabilityIds,
|
|
56
60
|
resolveManifestFiles,
|
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
RecommendedCapabilityIds,
|
|
17
17
|
StandaloneWasiCapabilityIds,
|
|
18
18
|
} from "../capabilities.js";
|
|
19
|
+
import {
|
|
20
|
+
PLG_FILE_IDENTIFIER,
|
|
21
|
+
decodePlgManifest,
|
|
22
|
+
isPlgManifestBuffer,
|
|
23
|
+
} from "../manifest/plgCodec.js";
|
|
19
24
|
|
|
20
25
|
const RecommendedCapabilitySet = new Set(RecommendedCapabilityIds);
|
|
21
26
|
const StandaloneWasiCapabilitySet = new Set(StandaloneWasiCapabilityIds);
|
|
@@ -1356,11 +1361,143 @@ export async function getWasmExportNamesFromFile(wasmPath) {
|
|
|
1356
1361
|
return getWasmExportNames(wasmBytes);
|
|
1357
1362
|
}
|
|
1358
1363
|
|
|
1364
|
+
const PLG_IDENTIFIER_BYTES = Uint8Array.from(
|
|
1365
|
+
Array.from(PLG_FILE_IDENTIFIER, (ch) => ch.charCodeAt(0)),
|
|
1366
|
+
);
|
|
1367
|
+
const LEGACY_PMAN_IDENTIFIER_BYTES = Uint8Array.from([0x50, 0x4d, 0x41, 0x4e]);
|
|
1368
|
+
|
|
1369
|
+
function indexOfBytes(haystack, needle, startIndex = 0) {
|
|
1370
|
+
if (!haystack || needle.length === 0 || haystack.length < needle.length) {
|
|
1371
|
+
return -1;
|
|
1372
|
+
}
|
|
1373
|
+
for (let i = startIndex; i <= haystack.length - needle.length; i += 1) {
|
|
1374
|
+
let matched = true;
|
|
1375
|
+
for (let j = 0; j < needle.length; j += 1) {
|
|
1376
|
+
if (haystack[i + j] !== needle[j]) {
|
|
1377
|
+
matched = false;
|
|
1378
|
+
break;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
if (matched) {
|
|
1382
|
+
return i;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return -1;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Scan wasm bytes for the canonical `$PLG` FlatBuffer identifier and, when
|
|
1390
|
+
* found, return both the raw offset and the smallest aligned candidate buffer
|
|
1391
|
+
* that a downstream decoder can try to parse. The wasm `data` sections embed
|
|
1392
|
+
* the manifest bytes verbatim; FlatBuffer headers are `<root_offset:u32><$PLG>`
|
|
1393
|
+
* so we walk backwards one 4-byte word and hand the caller a slice starting
|
|
1394
|
+
* there.
|
|
1395
|
+
*/
|
|
1396
|
+
export function locateEmbeddedPlgManifest(wasmBytes) {
|
|
1397
|
+
const bytes = wasmBytes instanceof Uint8Array
|
|
1398
|
+
? wasmBytes
|
|
1399
|
+
: new Uint8Array(wasmBytes);
|
|
1400
|
+
const identifierOffset = indexOfBytes(bytes, PLG_IDENTIFIER_BYTES);
|
|
1401
|
+
if (identifierOffset < 4) {
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
const start = identifierOffset - 4;
|
|
1405
|
+
// Upper bound on the manifest size — grab up to 64KB and let the caller
|
|
1406
|
+
// decide how much to trim. FlatBuffer decoders only read what they need.
|
|
1407
|
+
const end = Math.min(bytes.length, start + 65536);
|
|
1408
|
+
const candidate = bytes.subarray(start, end);
|
|
1409
|
+
if (!isPlgManifestBuffer(candidate)) {
|
|
1410
|
+
return null;
|
|
1411
|
+
}
|
|
1412
|
+
return { start, identifierOffset, bytes: candidate };
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
export function hasLegacyPmanManifest(wasmBytes) {
|
|
1416
|
+
const bytes = wasmBytes instanceof Uint8Array
|
|
1417
|
+
? wasmBytes
|
|
1418
|
+
: new Uint8Array(wasmBytes);
|
|
1419
|
+
return indexOfBytes(bytes, LEGACY_PMAN_IDENTIFIER_BYTES) !== -1;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function validateEmbeddedManifestBytes({
|
|
1423
|
+
wasmBytes,
|
|
1424
|
+
manifest,
|
|
1425
|
+
issues,
|
|
1426
|
+
sourceLabel,
|
|
1427
|
+
}) {
|
|
1428
|
+
if (hasLegacyPmanManifest(wasmBytes)) {
|
|
1429
|
+
pushIssue(
|
|
1430
|
+
issues,
|
|
1431
|
+
"error",
|
|
1432
|
+
"legacy-pman-manifest-embedded",
|
|
1433
|
+
"Plugin artifact embeds legacy PMAN manifest bytes — rebuild with the PLG codec.",
|
|
1434
|
+
sourceLabel,
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
const located = locateEmbeddedPlgManifest(wasmBytes);
|
|
1439
|
+
if (!located) {
|
|
1440
|
+
pushIssue(
|
|
1441
|
+
issues,
|
|
1442
|
+
"error",
|
|
1443
|
+
"missing-plg-manifest-bytes",
|
|
1444
|
+
`Plugin artifact does not embed a canonical \"${PLG_FILE_IDENTIFIER}\" manifest buffer.`,
|
|
1445
|
+
sourceLabel,
|
|
1446
|
+
);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
let decoded;
|
|
1451
|
+
try {
|
|
1452
|
+
decoded = decodePlgManifest(located.bytes);
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
pushIssue(
|
|
1455
|
+
issues,
|
|
1456
|
+
"error",
|
|
1457
|
+
"plg-manifest-decode-failed",
|
|
1458
|
+
`Failed to decode embedded PLG manifest: ${error?.message ?? error}`,
|
|
1459
|
+
sourceLabel,
|
|
1460
|
+
);
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const sourcePluginId = manifest?.pluginId ?? manifest?.plugin_id ?? null;
|
|
1465
|
+
if (
|
|
1466
|
+
isNonEmptyString(sourcePluginId) &&
|
|
1467
|
+
isNonEmptyString(decoded?.pluginId) &&
|
|
1468
|
+
sourcePluginId !== decoded.pluginId
|
|
1469
|
+
) {
|
|
1470
|
+
pushIssue(
|
|
1471
|
+
issues,
|
|
1472
|
+
"error",
|
|
1473
|
+
"plg-manifest-plugin-id-mismatch",
|
|
1474
|
+
`Embedded PLG manifest pluginId \"${decoded.pluginId}\" does not match source manifest pluginId \"${sourcePluginId}\".`,
|
|
1475
|
+
sourceLabel,
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
const sourceVersion = manifest?.version ?? null;
|
|
1480
|
+
if (
|
|
1481
|
+
isNonEmptyString(sourceVersion) &&
|
|
1482
|
+
isNonEmptyString(decoded?.version) &&
|
|
1483
|
+
sourceVersion !== decoded.version
|
|
1484
|
+
) {
|
|
1485
|
+
pushIssue(
|
|
1486
|
+
issues,
|
|
1487
|
+
"warning",
|
|
1488
|
+
"plg-manifest-version-mismatch",
|
|
1489
|
+
`Embedded PLG manifest version \"${decoded.version}\" does not match source manifest version \"${sourceVersion}\".`,
|
|
1490
|
+
sourceLabel,
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1359
1495
|
export async function validatePluginArtifact(options) {
|
|
1360
1496
|
const {
|
|
1361
1497
|
manifest,
|
|
1362
1498
|
manifestPath = null,
|
|
1363
1499
|
wasmPath = null,
|
|
1500
|
+
wasmBytes: inputWasmBytes = null,
|
|
1364
1501
|
exportNames = null,
|
|
1365
1502
|
sourceName = manifestPath ?? "manifest",
|
|
1366
1503
|
} = options;
|
|
@@ -1368,15 +1505,26 @@ export async function validatePluginArtifact(options) {
|
|
|
1368
1505
|
const issues = [...report.issues];
|
|
1369
1506
|
let resolvedExportNames = [];
|
|
1370
1507
|
let checkedArtifact = false;
|
|
1508
|
+
let loadedWasmBytes = null;
|
|
1371
1509
|
const declaredInvokeSurfaces = Array.isArray(manifest?.invokeSurfaces)
|
|
1372
1510
|
? manifest.invokeSurfaces.filter((surface) => InvokeSurfaceSet.has(surface))
|
|
1373
1511
|
: [];
|
|
1374
1512
|
|
|
1375
|
-
if (
|
|
1513
|
+
if (inputWasmBytes) {
|
|
1514
|
+
loadedWasmBytes =
|
|
1515
|
+
inputWasmBytes instanceof Uint8Array
|
|
1516
|
+
? inputWasmBytes
|
|
1517
|
+
: new Uint8Array(inputWasmBytes);
|
|
1518
|
+
resolvedExportNames = Array.isArray(exportNames)
|
|
1519
|
+
? [...exportNames]
|
|
1520
|
+
: getWasmExportNames(loadedWasmBytes);
|
|
1521
|
+
checkedArtifact = true;
|
|
1522
|
+
} else if (Array.isArray(exportNames)) {
|
|
1376
1523
|
resolvedExportNames = [...exportNames];
|
|
1377
1524
|
checkedArtifact = true;
|
|
1378
1525
|
} else if (isNonEmptyString(wasmPath)) {
|
|
1379
|
-
|
|
1526
|
+
loadedWasmBytes = new Uint8Array(await readFile(wasmPath));
|
|
1527
|
+
resolvedExportNames = getWasmExportNames(loadedWasmBytes);
|
|
1380
1528
|
checkedArtifact = true;
|
|
1381
1529
|
}
|
|
1382
1530
|
|
|
@@ -1424,6 +1572,14 @@ export async function validatePluginArtifact(options) {
|
|
|
1424
1572
|
wasmPath ?? sourceName,
|
|
1425
1573
|
);
|
|
1426
1574
|
}
|
|
1575
|
+
if (loadedWasmBytes) {
|
|
1576
|
+
validateEmbeddedManifestBytes({
|
|
1577
|
+
wasmBytes: loadedWasmBytes,
|
|
1578
|
+
manifest,
|
|
1579
|
+
issues,
|
|
1580
|
+
sourceLabel: wasmPath ?? sourceName,
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1427
1583
|
} else {
|
|
1428
1584
|
pushIssue(
|
|
1429
1585
|
issues,
|
package/src/embeddedManifest.js
CHANGED
|
@@ -2,8 +2,19 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { encodePluginManifest } from "./manifest/index.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { legacyManifestToPlg } from "./manifest/legacyToPlg.js";
|
|
6
|
+
import { encodePlgManifest } from "./manifest/plgCodec.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Convert a manifest input to the raw FlatBuffer bytes that will be
|
|
10
|
+
* embedded in the plugin artifact.
|
|
11
|
+
*
|
|
12
|
+
* The embedded manifest is the canonical spacedatastandards.org PLG
|
|
13
|
+
* schema (`$PLG` file_identifier) by default. Pass `format: "pman"` to
|
|
14
|
+
* emit the internal PluginManifest schema instead (legacy path, retained
|
|
15
|
+
* for tests that still compare against PMAN bytes).
|
|
16
|
+
*/
|
|
17
|
+
function toManifestBytes(manifest, format = "plg") {
|
|
7
18
|
if (manifest instanceof Uint8Array) {
|
|
8
19
|
return manifest;
|
|
9
20
|
}
|
|
@@ -17,7 +28,13 @@ function toManifestBytes(manifest) {
|
|
|
17
28
|
manifest.byteLength,
|
|
18
29
|
);
|
|
19
30
|
}
|
|
20
|
-
|
|
31
|
+
if (format === "pman") {
|
|
32
|
+
return encodePluginManifest(manifest);
|
|
33
|
+
}
|
|
34
|
+
// Default: PLG. Accept both PLG-shaped and legacy PluginManifest-shaped
|
|
35
|
+
// inputs; the converter is a no-op for PLG-native shapes.
|
|
36
|
+
const plgShape = legacyManifestToPlg(manifest);
|
|
37
|
+
return encodePlgManifest(plgShape);
|
|
21
38
|
}
|
|
22
39
|
|
|
23
40
|
function renderByteRows(bytes) {
|
|
@@ -34,7 +51,10 @@ function renderByteRows(bytes) {
|
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
export function generateEmbeddedManifestSource(options = {}) {
|
|
37
|
-
const manifestBytes = toManifestBytes(
|
|
54
|
+
const manifestBytes = toManifestBytes(
|
|
55
|
+
options.manifest,
|
|
56
|
+
options.format ?? "plg",
|
|
57
|
+
);
|
|
38
58
|
if (manifestBytes.length === 0) {
|
|
39
59
|
throw new Error("generateEmbeddedManifestSource requires manifest bytes.");
|
|
40
60
|
}
|
|
@@ -91,7 +111,7 @@ export function writeEmbeddedManifestArtifacts(options = {}) {
|
|
|
91
111
|
throw new Error("writeEmbeddedManifestArtifacts requires outputDir.");
|
|
92
112
|
}
|
|
93
113
|
|
|
94
|
-
const manifestBytes = toManifestBytes(manifest);
|
|
114
|
+
const manifestBytes = toManifestBytes(manifest, options.format ?? "plg");
|
|
95
115
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
96
116
|
|
|
97
117
|
const binaryPath = path.join(outputDir, binaryFileName);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// automatically generated by the FlatBuffers compiler, do not modify
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
|
3
|
-
import * as flatbuffers from "flatbuffers/
|
|
3
|
+
import * as flatbuffers from "../../../vendor/flatbuffers/flatbuffers.js";
|
|
4
4
|
import { TypedArenaBuffer } from '../../orbpro/stream/typed-arena-buffer.js';
|
|
5
5
|
/**
|
|
6
6
|
* Canonical invoke envelope consumed by direct ABI and command-mode execution.
|
|
@@ -128,4 +128,4 @@ export class PluginInvokeRequestT {
|
|
|
128
128
|
return PluginInvokeRequest.createPluginInvokeRequest(builder, methodId, inputFrames, payloadArena);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
-
//# sourceMappingURL=plugin-invoke-request.js.map
|
|
131
|
+
//# sourceMappingURL=plugin-invoke-request.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// automatically generated by the FlatBuffers compiler, do not modify
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
|
3
|
-
import * as flatbuffers from "flatbuffers/
|
|
3
|
+
import * as flatbuffers from "../../../vendor/flatbuffers/flatbuffers.js";
|
|
4
4
|
import { TypedArenaBuffer } from '../../orbpro/stream/typed-arena-buffer.js';
|
|
5
5
|
/**
|
|
6
6
|
* Canonical invoke result emitted by direct ABI and command-mode execution.
|
|
@@ -181,4 +181,4 @@ export class PluginInvokeResponseT {
|
|
|
181
181
|
return PluginInvokeResponse.createPluginInvokeResponse(builder, this.statusCode, this.yielded, this.backlogRemaining, outputFrames, payloadArena, errorCode, errorMessage);
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
-
//# sourceMappingURL=plugin-invoke-response.js.map
|
|
184
|
+
//# sourceMappingURL=plugin-invoke-response.js.map
|