space-data-module-sdk 0.8.5 → 0.8.7
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 +13 -0
- package/docs/AGENTS.md +5 -0
- package/docs/browser-wasmedge-isomorphic.md +64 -0
- package/docs/provider-access-abi.md +600 -0
- package/package.json +3 -2
- package/schemas/orbpro/Propagator.fbs +19 -3
- package/src/compiler/compileModule.js +24 -1
- package/src/flow/flowCompiler.js +141 -2
- package/src/flow/flowRuntimeHost.js +7 -1
- package/src/flow/vendor/sdn-flow/MethodRegistry.js +6 -1
- package/src/generated/orbpro/propagator/propagator-source-description.js +2 -2
- package/src/generated/orbpro/propagator/propagator-source-description.ts +2 -2
- package/src/generated/orbpro/propagator/propagator-source-kind.js +13 -2
- package/src/generated/orbpro/propagator/propagator-source-kind.ts +13 -2
- package/src/generated/spacedatastandards/plg/pluginCategory.d.ts +18 -1
- package/src/generated/spacedatastandards/plg/pluginCategory.d.ts.map +1 -1
- package/src/generated/spacedatastandards/plg/pluginCategory.js +17 -0
- package/src/generated/spacedatastandards/plg/pluginCategory.ts +21 -1
- package/src/host/index.js +6 -0
- package/src/host/providerAccess.js +727 -0
- package/src/host/providerAccessAbi.js +403 -0
- package/src/host/providerAccessEngineAdapter.js +338 -0
- package/src/host/providerAccessFixtureAdapter.js +444 -0
- package/src/host/providerAccessTileStoreAdapter.js +366 -0
- package/src/host/terrainSourceSeam.js +205 -0
- package/src/host/wasiThreadHost.js +12 -1
- package/src/index.d.ts +39 -4
- package/src/testing/browserModuleHarness.js +48 -0
- package/src/testing/index.d.ts +12 -1
- package/src/testing/parityBrowserRunner.js +8 -1
- package/src/testing/workerModuleHarness.js +36 -7
- package/src/testing/workerModuleHarnessWorker.js +5 -4
- package/src/transport/pki.js +57 -7
- package/templates/provider-access-module/include/space_data_provider_abi.h +227 -0
|
@@ -196,6 +196,37 @@ const DEFAULT_IMPORTED_MEMORY_MAXIMUM_BYTES = 2 * 1024 * 1024 * 1024;
|
|
|
196
196
|
const DEFAULT_DIRECT_INVOKE_REQUEST_ARENA_BYTES = 64 * 1024;
|
|
197
197
|
const SHARED_ARRAY_BUFFER_TAG = "[object SharedArrayBuffer]";
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Overwrite a plaintext wasm buffer in place. Best-effort by contract: a
|
|
201
|
+
* detached (transferred) buffer, a frozen typed array, or a runtime without
|
|
202
|
+
* writable views must not turn a successful module load into a thrown error.
|
|
203
|
+
*
|
|
204
|
+
* @param {Uint8Array|ArrayBuffer|ArrayBufferView|null|undefined} bytes
|
|
205
|
+
* @returns {number} bytes actually overwritten
|
|
206
|
+
*/
|
|
207
|
+
export function zeroWasmBytes(bytes) {
|
|
208
|
+
if (!bytes) {
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const view =
|
|
213
|
+
bytes instanceof Uint8Array
|
|
214
|
+
? bytes
|
|
215
|
+
: bytes instanceof ArrayBuffer
|
|
216
|
+
? new Uint8Array(bytes)
|
|
217
|
+
: ArrayBuffer.isView(bytes)
|
|
218
|
+
? new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
219
|
+
: null;
|
|
220
|
+
if (!view || view.byteLength === 0) {
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
view.fill(0);
|
|
224
|
+
return view.byteLength;
|
|
225
|
+
} catch {
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
199
230
|
export function isSharedArrayBufferLike(value) {
|
|
200
231
|
return (
|
|
201
232
|
value !== null &&
|
|
@@ -408,6 +439,11 @@ async function instantiateBrowserModule(options = {}) {
|
|
|
408
439
|
export async function createBrowserModuleHarness(options = {}) {
|
|
409
440
|
let wasmSource = options.wasmSource;
|
|
410
441
|
const signaturePolicy = resolveModuleSignaturePolicy(options);
|
|
442
|
+
// Bytes THIS function materializes itself (Response/URL fetch, or the
|
|
443
|
+
// ArrayBuffer/array copy taken for signature verification) — never a
|
|
444
|
+
// caller-owned buffer, which this function neither created nor may safely
|
|
445
|
+
// scrub. Zeroed below once the module is compiled.
|
|
446
|
+
let ownedArtifactBytes = null;
|
|
411
447
|
if (signaturePolicy) {
|
|
412
448
|
if (wasmSource instanceof WebAssembly.Module) {
|
|
413
449
|
throw new ModuleSignatureError(
|
|
@@ -434,8 +470,20 @@ export async function createBrowserModuleHarness(options = {}) {
|
|
|
434
470
|
}
|
|
435
471
|
await verifyModuleArtifact(artifactBytes, signaturePolicy);
|
|
436
472
|
wasmSource = artifactBytes;
|
|
473
|
+
ownedArtifactBytes = artifactBytes;
|
|
437
474
|
}
|
|
438
475
|
const wasmModule = await compileWasmModule(wasmSource);
|
|
476
|
+
// Plaintext hygiene (wasm-plaintext-memory-hygiene): nothing needs the
|
|
477
|
+
// source bytes past this point. Scrub whatever THIS function materialized
|
|
478
|
+
// itself, then drop every local/options reference to the source — a
|
|
479
|
+
// caller-supplied Uint8Array/ArrayBuffer must not outlive this call by
|
|
480
|
+
// being pinned via closure over `options` for the harness's entire
|
|
481
|
+
// lifetime (the returned harness closes over `options` for many other,
|
|
482
|
+
// unrelated fields).
|
|
483
|
+
zeroWasmBytes(ownedArtifactBytes);
|
|
484
|
+
ownedArtifactBytes = null;
|
|
485
|
+
wasmSource = null;
|
|
486
|
+
options.wasmSource = null;
|
|
439
487
|
const moduleImports = WebAssembly.Module.imports(wasmModule);
|
|
440
488
|
const needsHostBridge = moduleImports.some(
|
|
441
489
|
(entry) => entry.module === DEFAULT_HOSTCALL_IMPORT_MODULE,
|
package/src/testing/index.d.ts
CHANGED
|
@@ -439,9 +439,11 @@ export function resolveModuleHarnessLaunchPlan(options: {
|
|
|
439
439
|
export function detectArtifactProfile(wasmModule: WebAssembly.Module): string;
|
|
440
440
|
|
|
441
441
|
export function createBrowserModuleHarness(options?: {
|
|
442
|
-
wasmSource: Uint8Array | ArrayBuffer | string | WebAssembly.Module | unknown;
|
|
442
|
+
wasmSource: Uint8Array | ArrayBuffer | string | Response | WebAssembly.Module | unknown;
|
|
443
|
+
verifySignature?: boolean | { trustedPublicKeys?: string[]; requireSignature?: boolean };
|
|
443
444
|
host?: unknown;
|
|
444
445
|
hostOptions?: unknown;
|
|
446
|
+
hostcallDispatch?: (operation: string, params: unknown) => unknown;
|
|
445
447
|
args?: string[];
|
|
446
448
|
env?: Record<string, string>;
|
|
447
449
|
manifest?: Record<string, unknown>;
|
|
@@ -456,6 +458,15 @@ export function createBrowserModuleHarness(options?: {
|
|
|
456
458
|
allowRawInvoke?: boolean;
|
|
457
459
|
initialMemoryBytes?: number;
|
|
458
460
|
maximumMemoryBytes?: number;
|
|
461
|
+
maxThreads?: number;
|
|
462
|
+
threadHostcallChannel?: {
|
|
463
|
+
channelName: string;
|
|
464
|
+
token: string;
|
|
465
|
+
maxResponseBytes?: number;
|
|
466
|
+
timeoutMs?: number;
|
|
467
|
+
};
|
|
468
|
+
enableBrowserWasiThreads?: boolean;
|
|
469
|
+
directInvokeRequestArenaBytes?: number;
|
|
459
470
|
logOutput?: boolean;
|
|
460
471
|
}): Promise<BrowserModuleHarness>;
|
|
461
472
|
|
|
@@ -81,8 +81,15 @@ export async function runParityPlanInBrowser({ baseUrl = "" } = {}) {
|
|
|
81
81
|
let stderr = new Uint8Array(0);
|
|
82
82
|
let harness = null;
|
|
83
83
|
try {
|
|
84
|
+
// Plaintext hygiene: no per-case copy. createBrowserModuleHarness
|
|
85
|
+
// compiles directly from a caller-supplied buffer without copying it
|
|
86
|
+
// (and never mutates/zeroes a buffer it does not own — see
|
|
87
|
+
// browserModuleHarness.js's zeroWasmBytes/ownedArtifactBytes
|
|
88
|
+
// contract), so the one fetched `moduleBytes` is safe to reuse
|
|
89
|
+
// as-is across every case/thread-count iteration. A `.slice()` here
|
|
90
|
+
// would only add a fresh, unzeroed plaintext copy per run.
|
|
84
91
|
harness = await createBrowserModuleHarness({
|
|
85
|
-
wasmSource: moduleBytes
|
|
92
|
+
wasmSource: moduleBytes,
|
|
86
93
|
surface: "command",
|
|
87
94
|
args: planCase.args,
|
|
88
95
|
env: {
|
|
@@ -75,17 +75,46 @@ async function spawnWorker(workerUrl) {
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Compile `source` to a `WebAssembly.Module` on THIS (controlling) thread
|
|
80
|
+
* before ever touching a worker.
|
|
81
|
+
*
|
|
82
|
+
* Plaintext-hygiene contract (mirrors OrbPro's `wasmModuleHygiene.js`,
|
|
83
|
+
* `wasm-plaintext-memory-hygiene`): a compiled `WebAssembly.Module` is
|
|
84
|
+
* structured-cloneable to a Worker (browser) or `node:worker_threads`
|
|
85
|
+
* (Node) — proven by `createWasiThreadSpawn` in `src/host/wasiThreadHost.js`,
|
|
86
|
+
* which hands the same compiled module to every pooled worker in both
|
|
87
|
+
* lanes. Shipping the MODULE instead of raw bytes means there is no second
|
|
88
|
+
* plaintext copy alive in the worker, and no re-compile once it arrives —
|
|
89
|
+
* `createBrowserModuleHarness`'s own `compileWasmModule` already accepts a
|
|
90
|
+
* `WebAssembly.Module` directly and returns it unchanged.
|
|
91
|
+
*
|
|
92
|
+
* `source` is never retained past this call and never zeroed here: a
|
|
93
|
+
* caller-supplied `Uint8Array`/`ArrayBuffer` is not this harness's buffer to
|
|
94
|
+
* mutate (compile does not retain a reference to it either — the caller is
|
|
95
|
+
* free to reuse or scrub it immediately after this resolves).
|
|
96
|
+
*
|
|
97
|
+
* @param {WebAssembly.Module|Uint8Array|ArrayBuffer} source
|
|
98
|
+
* @param {string} label diagnostic label
|
|
99
|
+
* @returns {Promise<WebAssembly.Module>}
|
|
100
|
+
*/
|
|
101
|
+
async function toWasmModule(source, label) {
|
|
102
|
+
if (source instanceof WebAssembly.Module) {
|
|
103
|
+
return source;
|
|
104
|
+
}
|
|
105
|
+
if (source instanceof Uint8Array || source instanceof ArrayBuffer) {
|
|
106
|
+
return WebAssembly.compile(source);
|
|
107
|
+
}
|
|
81
108
|
throw new TypeError(
|
|
82
|
-
`${label} must be a Uint8Array or ArrayBuffer
|
|
109
|
+
`${label} must be a WebAssembly.Module, Uint8Array, or ArrayBuffer.`,
|
|
83
110
|
);
|
|
84
111
|
}
|
|
85
112
|
|
|
86
113
|
/**
|
|
87
114
|
* @param {object} options
|
|
88
|
-
* @param {Uint8Array|ArrayBuffer} options.wasmSource module
|
|
115
|
+
* @param {WebAssembly.Module|Uint8Array|ArrayBuffer} options.wasmSource module
|
|
116
|
+
* bytes OR an already-compiled module (preferred — skips a redundant
|
|
117
|
+
* compile and avoids ever materializing plaintext bytes in this harness)
|
|
89
118
|
* @param {object} [options.host] host servicing guest hostcalls (default createBrowserHost(hostOptions))
|
|
90
119
|
* @param {object} [options.hostOptions] options for the default host
|
|
91
120
|
* @param {(operation: string, params: any) => Promise<any>} [options.dispatchHost] dispatch override
|
|
@@ -96,7 +125,7 @@ function toBytes(source, label) {
|
|
|
96
125
|
* createBrowserModuleHarness (surface, args, env, allowRawInvoke, logOutput, ...)
|
|
97
126
|
*/
|
|
98
127
|
export async function createWorkerModuleHarness(options = {}) {
|
|
99
|
-
const
|
|
128
|
+
const wasmModule = await toWasmModule(options.wasmSource, "wasmSource");
|
|
100
129
|
const host = options.host ?? createBrowserHost(options.hostOptions);
|
|
101
130
|
const dispatch = options.dispatchHost ?? createAsyncHostDispatcher(host);
|
|
102
131
|
const buffer = createSabHostcallBuffer({
|
|
@@ -150,7 +179,7 @@ export async function createWorkerModuleHarness(options = {}) {
|
|
|
150
179
|
case "worker-online":
|
|
151
180
|
port.post({
|
|
152
181
|
type: "init",
|
|
153
|
-
|
|
182
|
+
wasmModule,
|
|
154
183
|
buffer,
|
|
155
184
|
hostcallTimeoutMs: options.hostcallTimeoutMs,
|
|
156
185
|
harnessOptions: {
|
|
@@ -90,12 +90,13 @@ async function handleInit(message) {
|
|
|
90
90
|
timeoutMs: message.hostcallTimeoutMs,
|
|
91
91
|
postRequest: (request) => port.post({ type: "hostcall", ...request }),
|
|
92
92
|
});
|
|
93
|
+
// Plaintext-hygiene contract: the controlling thread compiles once and
|
|
94
|
+
// ships the WebAssembly.Module (never raw bytes) — see toWasmModule() in
|
|
95
|
+
// workerModuleHarness.js. createBrowserModuleHarness's compileWasmModule
|
|
96
|
+
// already returns a Module input unchanged, so this never re-compiles.
|
|
93
97
|
const harnessOptions = {
|
|
94
98
|
...(message.harnessOptions ?? {}),
|
|
95
|
-
wasmSource:
|
|
96
|
-
message.wasmBytes instanceof Uint8Array
|
|
97
|
-
? message.wasmBytes
|
|
98
|
-
: new Uint8Array(message.wasmBytes),
|
|
99
|
+
wasmSource: message.wasmModule,
|
|
99
100
|
hostcallDispatch: dispatch,
|
|
100
101
|
};
|
|
101
102
|
harness = await createBrowserModuleHarness(harnessOptions);
|
package/src/transport/pki.js
CHANGED
|
@@ -80,6 +80,45 @@ async function deriveAesKey(sharedSecret, salt, context) {
|
|
|
80
80
|
);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// SAW-M3: the recipient's raw private scalar never has to enter this module.
|
|
84
|
+
// A caller (e.g. a browser session backed by hd-wallet-wasm) may supply a
|
|
85
|
+
// `keyAgreement` provider that performs step 1 (the X25519 ECDH, the ONLY
|
|
86
|
+
// step that needs the scalar) behind its own custody boundary and resolves
|
|
87
|
+
// the 32-byte shared secret. Steps 2-3 (HKDF derive, AES-GCM) stay here —
|
|
88
|
+
// public math, curve-agnostic, not duplicated into the wallet.
|
|
89
|
+
function keyExchangeFromAlgorithm(algorithm) {
|
|
90
|
+
if (typeof algorithm !== "string" || !algorithm.length) {
|
|
91
|
+
return "X25519";
|
|
92
|
+
}
|
|
93
|
+
return algorithm.split("-")[0] || "X25519";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function resolveSharedSecret(
|
|
97
|
+
label,
|
|
98
|
+
{ recipientPrivateKey, keyAgreement, ephemeralPublicKey, context, keyExchange },
|
|
99
|
+
) {
|
|
100
|
+
if (recipientPrivateKey && keyAgreement) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`${label} accepts either recipientPrivateKey or keyAgreement, not both.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (keyAgreement) {
|
|
106
|
+
const sharedSecret = toUint8Array(
|
|
107
|
+
await keyAgreement({ ephemeralPublicKey, context, keyExchange }),
|
|
108
|
+
);
|
|
109
|
+
if (sharedSecret.length !== 32) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`${label} keyAgreement provider must resolve a 32-byte shared secret.`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return sharedSecret;
|
|
115
|
+
}
|
|
116
|
+
if (!recipientPrivateKey) {
|
|
117
|
+
throw new Error(`${label} requires recipientPrivateKey or keyAgreement.`);
|
|
118
|
+
}
|
|
119
|
+
return deriveSharedSecret(recipientPrivateKey, ephemeralPublicKey);
|
|
120
|
+
}
|
|
121
|
+
|
|
83
122
|
function normalizeOptionalBytes(value) {
|
|
84
123
|
if (value === null || value === undefined) {
|
|
85
124
|
return new Uint8Array(0);
|
|
@@ -308,15 +347,22 @@ export async function protectMarketplaceContent({
|
|
|
308
347
|
export async function decryptMarketplaceContentKeyWrap({
|
|
309
348
|
wrap,
|
|
310
349
|
recipientPrivateKey,
|
|
350
|
+
keyAgreement,
|
|
311
351
|
} = {}) {
|
|
312
352
|
if (!wrap) {
|
|
313
353
|
throw new Error("decryptMarketplaceContentKeyWrap requires wrap.");
|
|
314
354
|
}
|
|
315
|
-
const sharedSecret = await deriveSharedSecret(
|
|
316
|
-
recipientPrivateKey,
|
|
317
|
-
wrap.providerEphemeralPublicKey,
|
|
318
|
-
);
|
|
319
355
|
const wrapContext = `marketplace-content-key:${wrap.providerId}:${wrap.contentKeyId}:${wrap.recipientKeyId}`;
|
|
356
|
+
const sharedSecret = await resolveSharedSecret(
|
|
357
|
+
"decryptMarketplaceContentKeyWrap",
|
|
358
|
+
{
|
|
359
|
+
recipientPrivateKey,
|
|
360
|
+
keyAgreement,
|
|
361
|
+
ephemeralPublicKey: wrap.providerEphemeralPublicKey,
|
|
362
|
+
context: wrapContext,
|
|
363
|
+
keyExchange: keyExchangeFromAlgorithm(wrap.algorithm),
|
|
364
|
+
},
|
|
365
|
+
);
|
|
320
366
|
const wrapKey = await deriveAesKey(sharedSecret, new Uint8Array(0), wrapContext);
|
|
321
367
|
const keyMaterial = await aesGcmDecrypt(
|
|
322
368
|
wrapKey,
|
|
@@ -364,16 +410,20 @@ async function encryptBytesLegacy({
|
|
|
364
410
|
export async function decryptProtectedBytes({
|
|
365
411
|
protectedBytes,
|
|
366
412
|
recipientPrivateKey,
|
|
413
|
+
keyAgreement,
|
|
367
414
|
} = {}) {
|
|
368
415
|
const parsed = extractPublicationRecordCollection(protectedBytes);
|
|
369
416
|
if (!parsed?.enc) {
|
|
370
417
|
return toUint8Array(protectedBytes);
|
|
371
418
|
}
|
|
372
419
|
assertGcmEncRecord(parsed.enc);
|
|
373
|
-
const sharedSecret = await
|
|
420
|
+
const sharedSecret = await resolveSharedSecret("decryptProtectedBytes", {
|
|
374
421
|
recipientPrivateKey,
|
|
375
|
-
|
|
376
|
-
|
|
422
|
+
keyAgreement,
|
|
423
|
+
ephemeralPublicKey: parsed.enc.ephemeralPublicKey,
|
|
424
|
+
context: parsed.enc.context ?? "",
|
|
425
|
+
keyExchange: parsed.enc.keyExchange,
|
|
426
|
+
});
|
|
377
427
|
const aesKey = await deriveAesKey(
|
|
378
428
|
sharedSecret,
|
|
379
429
|
new Uint8Array(0),
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Provider Access ABI — guest header.
|
|
3
|
+
*
|
|
4
|
+
* One generalized port for imagery/terrain providers. Identical import names
|
|
5
|
+
* and signatures in the browser, in native WasmEdge and in Docker WasmEdge.
|
|
6
|
+
*
|
|
7
|
+
* Build: clang --target=wasm32-wasip1-threads (never emcc -pthread).
|
|
8
|
+
* The module is EH-free and there is exactly one dist/isomorphic/module.wasm.
|
|
9
|
+
*
|
|
10
|
+
* NOTE ON TYPES: every parameter and every result below is int32_t. No int64_t
|
|
11
|
+
* appears anywhere in a boundary signature. This is deliberate — a 64-bit
|
|
12
|
+
* import parameter legalizes differently depending on how the host
|
|
13
|
+
* instantiates the module, and the mismatch shows up as a link failure or a
|
|
14
|
+
* silently truncated argument in exactly one runtime. Every 64-bit quantity in
|
|
15
|
+
* this ABI travels inside the descriptor struct below, in guest memory, as
|
|
16
|
+
* plain little-endian IEEE-754 that you read with a normal load.
|
|
17
|
+
*
|
|
18
|
+
* See docs/provider-access-abi.md for the normative contract.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
#ifndef SPACE_DATA_PROVIDER_ABI_H
|
|
22
|
+
#define SPACE_DATA_PROVIDER_ABI_H
|
|
23
|
+
|
|
24
|
+
#include <stdint.h>
|
|
25
|
+
|
|
26
|
+
#ifdef __cplusplus
|
|
27
|
+
extern "C" {
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
#define SDM_PROVIDER_IMPORT_MODULE "space_data_provider"
|
|
31
|
+
#define SDM_PROVIDER_ABI_VERSION 1
|
|
32
|
+
#define SDM_PROVIDER_TILE_DESC_MAGIC 0x53445054u /* 'SDPT' */
|
|
33
|
+
#define SDM_PROVIDER_TILE_DESC_BYTES 128
|
|
34
|
+
|
|
35
|
+
/* ---- kinds ------------------------------------------------------------- */
|
|
36
|
+
#define SDM_PROVIDER_KIND_TERRAIN 1u
|
|
37
|
+
#define SDM_PROVIDER_KIND_IMAGERY 2u
|
|
38
|
+
|
|
39
|
+
/* ---- encodings --------------------------------------------------------- */
|
|
40
|
+
#define SDM_PROVIDER_ENC_HEIGHT_F32 1u /* float, metres above the ellipsoid */
|
|
41
|
+
#define SDM_PROVIDER_ENC_HEIGHT_F64 2u /* double, metres above the ellipsoid */
|
|
42
|
+
#define SDM_PROVIDER_ENC_RGBA8 16u
|
|
43
|
+
#define SDM_PROVIDER_ENC_RGB8 17u
|
|
44
|
+
#define SDM_PROVIDER_ENC_GRAY8 18u
|
|
45
|
+
#define SDM_PROVIDER_ENC_GRAY16 19u
|
|
46
|
+
#define SDM_PROVIDER_ENC_RGBA_F32 20u
|
|
47
|
+
|
|
48
|
+
/* ---- flags ------------------------------------------------------------- */
|
|
49
|
+
#define SDM_PROVIDER_FLAG_INTERPOLATED (1u << 0)
|
|
50
|
+
#define SDM_PROVIDER_FLAG_STAGED (1u << 1)
|
|
51
|
+
#define SDM_PROVIDER_FLAG_PARTIAL (1u << 2)
|
|
52
|
+
#define SDM_PROVIDER_FLAG_DERIVED (1u << 3)
|
|
53
|
+
#define SDM_PROVIDER_FLAG_FIXTURE (1u << 4)
|
|
54
|
+
|
|
55
|
+
/* ---- cost classes ------------------------------------------------------ *
|
|
56
|
+
* "maxCost" in the acquire request defaults to DEQUANTIZE. That default IS
|
|
57
|
+
* the "never re-fetch, never re-parse" rule, enforced by the ABI rather than
|
|
58
|
+
* by discipline. Raising it is how a caller says out loud that it accepts a
|
|
59
|
+
* cost.
|
|
60
|
+
*/
|
|
61
|
+
#define SDM_PROVIDER_COST_RESIDENT 0u
|
|
62
|
+
#define SDM_PROVIDER_COST_DEQUANTIZE 1u
|
|
63
|
+
#define SDM_PROVIDER_COST_REDECODE 2u
|
|
64
|
+
#define SDM_PROVIDER_COST_REFETCH 3u
|
|
65
|
+
#define SDM_PROVIDER_COST_READBACK 4u
|
|
66
|
+
|
|
67
|
+
/* ---- level-selection provenance ---------------------------------------- *
|
|
68
|
+
* A consumer that cannot see which level answered cannot tell a solve that
|
|
69
|
+
* resolved the ridges from one that interpolated them away. Names mirror the
|
|
70
|
+
* engine consumer's existing vocabulary.
|
|
71
|
+
*/
|
|
72
|
+
#define SDM_PROVIDER_STRATEGY_DEFAULT 0u
|
|
73
|
+
#define SDM_PROVIDER_STRATEGY_GRID_MATCHED_LEVEL 1u
|
|
74
|
+
#define SDM_PROVIDER_STRATEGY_MOST_DETAILED 2u
|
|
75
|
+
#define SDM_PROVIDER_STRATEGY_FIXED_LEVEL 3u
|
|
76
|
+
|
|
77
|
+
/* ---- error codes ------------------------------------------------------- *
|
|
78
|
+
* Negative results from acquire/read/release. Identical value, identical
|
|
79
|
+
* meaning and identical trap class (none — these are VALUES) in every runtime.
|
|
80
|
+
*/
|
|
81
|
+
#define SDM_PROVIDER_E_INVALID_REQUEST (-1)
|
|
82
|
+
#define SDM_PROVIDER_E_NO_CAPABILITY (-2)
|
|
83
|
+
#define SDM_PROVIDER_E_NO_PROVIDER (-3)
|
|
84
|
+
#define SDM_PROVIDER_E_NOT_READY (-4)
|
|
85
|
+
#define SDM_PROVIDER_E_NOT_AVAILABLE (-5)
|
|
86
|
+
#define SDM_PROVIDER_E_BOUNDS (-6)
|
|
87
|
+
#define SDM_PROVIDER_E_BAD_HANDLE (-7)
|
|
88
|
+
#define SDM_PROVIDER_E_BAD_PLANE (-8)
|
|
89
|
+
#define SDM_PROVIDER_E_UNSUPPORTED (-9)
|
|
90
|
+
#define SDM_PROVIDER_E_TIMEOUT (-10)
|
|
91
|
+
#define SDM_PROVIDER_E_HOST (-11)
|
|
92
|
+
#define SDM_PROVIDER_E_PORT_UNAVAILABLE (-12)
|
|
93
|
+
|
|
94
|
+
/* ---- no-data sentinel -------------------------------------------------- *
|
|
95
|
+
* NOT NaN. WebAssembly does not canonicalize NaN payloads across every
|
|
96
|
+
* producing operation, so two runtimes can hold different bits for "a NaN" and
|
|
97
|
+
* a byte-identical-output assertion would fail on semantically equal values.
|
|
98
|
+
* These have exactly one encoding each and are never a real terrain height.
|
|
99
|
+
*/
|
|
100
|
+
#define SDM_PROVIDER_NO_DATA_F32_BITS 0xFF7FFFFFu
|
|
101
|
+
#define SDM_PROVIDER_NO_DATA_F64_BITS 0xFFEFFFFFFFFFFFFFull
|
|
102
|
+
|
|
103
|
+
static inline int sdm_provider_is_no_data_f64(double value) {
|
|
104
|
+
uint64_t bits;
|
|
105
|
+
__builtin_memcpy(&bits, &value, sizeof bits);
|
|
106
|
+
return bits == SDM_PROVIDER_NO_DATA_F64_BITS;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static inline int sdm_provider_is_no_data_f32(float value) {
|
|
110
|
+
uint32_t bits;
|
|
111
|
+
__builtin_memcpy(&bits, &value, sizeof bits);
|
|
112
|
+
return bits == SDM_PROVIDER_NO_DATA_F32_BITS;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* ---- tile descriptor --------------------------------------------------- *
|
|
116
|
+
* 128 bytes, little-endian. All f64 fields are 8-byte aligned, so this struct
|
|
117
|
+
* is read with ordinary aligned loads on every target.
|
|
118
|
+
*/
|
|
119
|
+
typedef struct sdm_provider_tile_desc {
|
|
120
|
+
uint32_t magic; /* 0 SDM_PROVIDER_TILE_DESC_MAGIC */
|
|
121
|
+
uint32_t version; /* 4 SDM_PROVIDER_ABI_VERSION */
|
|
122
|
+
uint32_t kind; /* 8 */
|
|
123
|
+
uint32_t encoding; /* 12 */
|
|
124
|
+
uint32_t width; /* 16 elements per row */
|
|
125
|
+
uint32_t height; /* 20 rows */
|
|
126
|
+
uint32_t plane_count; /* 24 */
|
|
127
|
+
uint32_t bytes_per_element; /* 28 */
|
|
128
|
+
uint32_t row_stride_bytes; /* 32 */
|
|
129
|
+
uint32_t byte_length; /* 36 plane 0 */
|
|
130
|
+
uint32_t flags; /* 40 */
|
|
131
|
+
uint32_t level; /* 44 0xFFFFFFFF for derived tiles */
|
|
132
|
+
double west; /* 48 radians */
|
|
133
|
+
double south; /* 56 */
|
|
134
|
+
double east; /* 64 */
|
|
135
|
+
double north; /* 72 */
|
|
136
|
+
double min_value; /* 80 terrain: min height, metres */
|
|
137
|
+
double max_value; /* 88 terrain: max height, metres */
|
|
138
|
+
uint32_t tile_x; /* 96 */
|
|
139
|
+
uint32_t tile_y; /* 100 */
|
|
140
|
+
uint32_t host_copies; /* 104 host->guest copies per whole-plane read */
|
|
141
|
+
uint32_t source_id; /* 108 FNV-1a 32 of the provider id */
|
|
142
|
+
uint32_t cost_class; /* 112 what this acquire actually cost */
|
|
143
|
+
uint32_t strategy; /* 116 how the level was chosen */
|
|
144
|
+
uint32_t reserved[2]; /* 120 */
|
|
145
|
+
} sdm_provider_tile_desc_t;
|
|
146
|
+
|
|
147
|
+
_Static_assert(sizeof(sdm_provider_tile_desc_t) == SDM_PROVIDER_TILE_DESC_BYTES,
|
|
148
|
+
"sdm_provider_tile_desc_t must be exactly 128 bytes");
|
|
149
|
+
|
|
150
|
+
/* ---- imports ----------------------------------------------------------- *
|
|
151
|
+
* Open, read, close. Three doors.
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
* Acquire pins a provider's decoded buffer host-side and fills `desc`.
|
|
156
|
+
* `request` is UTF-8 JSON:
|
|
157
|
+
*
|
|
158
|
+
* {"op":"tile","providerId":"...","level":9,"x":1,"y":2}
|
|
159
|
+
* {"op":"profile","providerId":"...","start":[lon,lat],"end":[lon,lat],
|
|
160
|
+
* "samples":256,"level":"mostDetailed"}
|
|
161
|
+
* {"op":"profile","providerId":"...","positionsPtr":P,"positionsCount":N}
|
|
162
|
+
* {"op":"region","providerId":"...","rectangle":[w,s,e,n],
|
|
163
|
+
* "width":256,"height":256,"level":9}
|
|
164
|
+
*
|
|
165
|
+
* plus optional "maxCost" (default 1), "plane", and "spacing" (the target
|
|
166
|
+
* sample spacing in METRES — preferred over "level": you know your own march
|
|
167
|
+
* stride, you do not know a provider's level scheme).
|
|
168
|
+
*
|
|
169
|
+
* RASTER IN: a coverage field is commonly 512x512 = 262,144 positions.
|
|
170
|
+
* Encoding those as JSON would be a multi-megabyte request string parsed on
|
|
171
|
+
* every call — a worse cost than the read it is asking for. Pass
|
|
172
|
+
* "positionsPtr"/"positionsCount" instead and the host reads interleaved f64
|
|
173
|
+
* lon/lat pairs straight out of guest memory with ONE copy, symmetric with the
|
|
174
|
+
* way results come back.
|
|
175
|
+
*
|
|
176
|
+
* Returns a handle > 0, or a negative SDM_PROVIDER_E_* code. `desc` is left
|
|
177
|
+
* untouched on failure. The call blocks until the tile is resident.
|
|
178
|
+
*/
|
|
179
|
+
__attribute__((import_module(SDM_PROVIDER_IMPORT_MODULE),
|
|
180
|
+
import_name("acquire"))) extern int32_t
|
|
181
|
+
sdm_provider_acquire_raw(int32_t request_ptr, int32_t request_len,
|
|
182
|
+
int32_t desc_ptr);
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
* Copies at most `dst_len` bytes of `plane`, starting `src_offset` bytes into
|
|
186
|
+
* that plane, into guest memory at `dst_ptr`. Returns bytes written (which may
|
|
187
|
+
* legally be less than dst_len at the tail), or a negative error code.
|
|
188
|
+
*
|
|
189
|
+
* Reading a plane in several chunks yields exactly the same bytes as one
|
|
190
|
+
* whole-plane read.
|
|
191
|
+
*/
|
|
192
|
+
__attribute__((import_module(SDM_PROVIDER_IMPORT_MODULE),
|
|
193
|
+
import_name("read"))) extern int32_t
|
|
194
|
+
sdm_provider_read_raw(int32_t handle, int32_t plane, int32_t src_offset,
|
|
195
|
+
int32_t dst_ptr, int32_t dst_len);
|
|
196
|
+
|
|
197
|
+
/* Unpins. Releasing an already-released handle returns E_BAD_HANDLE. */
|
|
198
|
+
__attribute__((import_module(SDM_PROVIDER_IMPORT_MODULE),
|
|
199
|
+
import_name("release"))) extern int32_t
|
|
200
|
+
sdm_provider_release(int32_t handle);
|
|
201
|
+
|
|
202
|
+
/* ---- pointer-friendly wrappers ---------------------------------------- */
|
|
203
|
+
|
|
204
|
+
static inline int32_t sdm_provider_acquire(const char *request,
|
|
205
|
+
int32_t request_len,
|
|
206
|
+
sdm_provider_tile_desc_t *desc) {
|
|
207
|
+
return sdm_provider_acquire_raw((int32_t)(uintptr_t)request, request_len,
|
|
208
|
+
(int32_t)(uintptr_t)desc);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static inline int32_t sdm_provider_read(int32_t handle, int32_t plane,
|
|
212
|
+
int32_t src_offset, void *dst,
|
|
213
|
+
int32_t dst_len) {
|
|
214
|
+
return sdm_provider_read_raw(handle, plane, src_offset,
|
|
215
|
+
(int32_t)(uintptr_t)dst, dst_len);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static inline int sdm_provider_desc_valid(const sdm_provider_tile_desc_t *desc) {
|
|
219
|
+
return desc != 0 && desc->magic == SDM_PROVIDER_TILE_DESC_MAGIC &&
|
|
220
|
+
desc->version == SDM_PROVIDER_ABI_VERSION;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#ifdef __cplusplus
|
|
224
|
+
} /* extern "C" */
|
|
225
|
+
#endif
|
|
226
|
+
|
|
227
|
+
#endif /* SPACE_DATA_PROVIDER_ABI_H */
|