space-data-module-sdk 0.5.14 → 0.5.15
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 +67 -1
- package/package.json +1 -1
- package/src/compliance/pluginCompliance.js +1 -131
- package/src/index.d.ts +18 -1
- package/src/invoke/codec.js +9 -7
- package/src/manifest/typeRefs.js +12 -49
- package/src/testing/buildWasmEdgeRunner.js +214 -0
- package/src/testing/index.d.ts +190 -0
- package/src/testing/index.js +20 -0
- package/src/testing/moduleHarness.js +62 -0
- package/src/testing/native/wasmedge_emscripten_pthread_runner.c +1001 -0
- package/src/testing/processInvoke.js +226 -0
- package/src/testing/publicationProtectionDemo.js +271 -0
- package/src/transport/records.js +41 -53
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { once } from "node:events";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
decodePluginInvokeResponse,
|
|
9
|
+
encodePluginInvokeRequest,
|
|
10
|
+
} from "../invoke/index.js";
|
|
11
|
+
import { toUint8Array } from "../runtime/bufferLike.js";
|
|
12
|
+
|
|
13
|
+
function formatProcessFailure(message, stderrChunks = [], cause = null) {
|
|
14
|
+
const stderrText = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
15
|
+
const details = stderrText ? `${message}\n${stderrText}` : message;
|
|
16
|
+
return cause ? new Error(details, { cause }) : new Error(details);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createLengthPrefixedRequest(bytes) {
|
|
20
|
+
const payload = Buffer.from(bytes);
|
|
21
|
+
const prefix = Buffer.allocUnsafe(4);
|
|
22
|
+
prefix.writeUInt32LE(payload.length, 0);
|
|
23
|
+
return Buffer.concat([prefix, payload]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeLaunchPlan(options = {}) {
|
|
27
|
+
if (options.launchPlan) {
|
|
28
|
+
return {
|
|
29
|
+
...options.launchPlan,
|
|
30
|
+
args: Array.isArray(options.launchPlan.args) ? options.launchPlan.args : [],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
command: options.command ?? null,
|
|
35
|
+
args: Array.isArray(options.args) ? options.args : [],
|
|
36
|
+
env: options.env ?? process.env,
|
|
37
|
+
cwd: options.cwd ?? process.cwd(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildWasmEdgeSpawnEnv(baseEnv = process.env) {
|
|
42
|
+
const env = { ...baseEnv };
|
|
43
|
+
delete env.DYLD_LIBRARY_PATH;
|
|
44
|
+
delete env.DYLD_FALLBACK_LIBRARY_PATH;
|
|
45
|
+
delete env.DYLD_FRAMEWORK_PATH;
|
|
46
|
+
delete env.DYLD_FALLBACK_FRAMEWORK_PATH;
|
|
47
|
+
delete env.LIBRARY_PATH;
|
|
48
|
+
return env;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function resolveWasmEdgePluginLaunchPlan(options = {}) {
|
|
52
|
+
const wasmPath =
|
|
53
|
+
typeof options.wasmPath === "string" && options.wasmPath.trim().length > 0
|
|
54
|
+
? path.resolve(options.wasmPath)
|
|
55
|
+
: null;
|
|
56
|
+
if (!wasmPath) {
|
|
57
|
+
throw new Error("resolveWasmEdgePluginLaunchPlan requires a wasmPath.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const invokeArgs =
|
|
61
|
+
Array.isArray(options.invokeArgs) && options.invokeArgs.length > 0
|
|
62
|
+
? [...options.invokeArgs]
|
|
63
|
+
: ["--serve-plugin-invoke"];
|
|
64
|
+
|
|
65
|
+
if (options.wasmEdgeRunnerBinary) {
|
|
66
|
+
return {
|
|
67
|
+
command: options.wasmEdgeRunnerBinary,
|
|
68
|
+
args: [wasmPath, ...invokeArgs],
|
|
69
|
+
env: buildWasmEdgeSpawnEnv(options.env),
|
|
70
|
+
wasmPath,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
command: options.wasmEdgeBinary ?? "wasmedge",
|
|
76
|
+
args: [
|
|
77
|
+
...(options.enableThreads === false ? [] : ["--enable-threads"]),
|
|
78
|
+
wasmPath,
|
|
79
|
+
...invokeArgs,
|
|
80
|
+
],
|
|
81
|
+
env: buildWasmEdgeSpawnEnv(options.env),
|
|
82
|
+
wasmPath,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function createPluginInvokeProcessClient(options = {}) {
|
|
87
|
+
const launchPlan = normalizeLaunchPlan(options);
|
|
88
|
+
if (
|
|
89
|
+
typeof launchPlan.command !== "string" ||
|
|
90
|
+
launchPlan.command.trim().length === 0
|
|
91
|
+
) {
|
|
92
|
+
throw new Error("createPluginInvokeProcessClient requires a command.");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const child = spawn(launchPlan.command, launchPlan.args, {
|
|
96
|
+
cwd: launchPlan.cwd ?? process.cwd(),
|
|
97
|
+
env: launchPlan.env ?? process.env,
|
|
98
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
let stdoutBuffer = Buffer.alloc(0);
|
|
102
|
+
const stderrChunks = [];
|
|
103
|
+
const pending = [];
|
|
104
|
+
let closed = false;
|
|
105
|
+
let closeError = null;
|
|
106
|
+
let expectedShutdown = false;
|
|
107
|
+
|
|
108
|
+
function rejectPending(error) {
|
|
109
|
+
while (pending.length > 0) {
|
|
110
|
+
pending.shift().reject(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function drainResponses() {
|
|
115
|
+
while (pending.length > 0 && stdoutBuffer.length >= 4) {
|
|
116
|
+
const responseLength = stdoutBuffer.readUInt32LE(0);
|
|
117
|
+
if (stdoutBuffer.length < 4 + responseLength) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const responseBytes = stdoutBuffer.subarray(4, 4 + responseLength);
|
|
121
|
+
stdoutBuffer = stdoutBuffer.subarray(4 + responseLength);
|
|
122
|
+
pending.shift().resolve(new Uint8Array(responseBytes));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
child.stdout.on("data", (chunk) => {
|
|
127
|
+
stdoutBuffer = Buffer.concat([stdoutBuffer, Buffer.from(chunk)]);
|
|
128
|
+
drainResponses();
|
|
129
|
+
});
|
|
130
|
+
child.stderr.on("data", (chunk) => {
|
|
131
|
+
stderrChunks.push(Buffer.from(chunk));
|
|
132
|
+
});
|
|
133
|
+
child.on("error", (error) => {
|
|
134
|
+
closeError = formatProcessFailure(
|
|
135
|
+
"Failed to launch plugin invoke process.",
|
|
136
|
+
stderrChunks,
|
|
137
|
+
error,
|
|
138
|
+
);
|
|
139
|
+
rejectPending(closeError);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const closePromise = once(child, "close").then(([code, signal]) => {
|
|
143
|
+
closed = true;
|
|
144
|
+
if (!expectedShutdown && (code !== 0 || signal !== null)) {
|
|
145
|
+
closeError = formatProcessFailure(
|
|
146
|
+
`Plugin invoke process exited unexpectedly with ${
|
|
147
|
+
signal ? `signal ${signal}` : `code ${code}`
|
|
148
|
+
}.`,
|
|
149
|
+
stderrChunks,
|
|
150
|
+
);
|
|
151
|
+
rejectPending(closeError);
|
|
152
|
+
throw closeError;
|
|
153
|
+
}
|
|
154
|
+
if (!expectedShutdown && code !== 0) {
|
|
155
|
+
closeError = formatProcessFailure(
|
|
156
|
+
`Plugin invoke process exited with code ${code}.`,
|
|
157
|
+
stderrChunks,
|
|
158
|
+
);
|
|
159
|
+
rejectPending(closeError);
|
|
160
|
+
throw closeError;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
async function invokeRaw(requestBytes) {
|
|
165
|
+
if (closeError) {
|
|
166
|
+
throw closeError;
|
|
167
|
+
}
|
|
168
|
+
if (closed) {
|
|
169
|
+
throw formatProcessFailure(
|
|
170
|
+
"Plugin invoke process is already closed.",
|
|
171
|
+
stderrChunks,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const normalizedRequest = toUint8Array(requestBytes);
|
|
176
|
+
if (!normalizedRequest) {
|
|
177
|
+
throw new TypeError(
|
|
178
|
+
"Expected Uint8Array, ArrayBufferView, or ArrayBuffer request bytes.",
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
pending.push({ resolve, reject });
|
|
184
|
+
child.stdin.write(createLengthPrefixedRequest(normalizedRequest), (error) => {
|
|
185
|
+
if (!error) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const pendingIndex = pending.findIndex((entry) => entry.resolve === resolve);
|
|
189
|
+
if (pendingIndex >= 0) {
|
|
190
|
+
pending.splice(pendingIndex, 1);
|
|
191
|
+
}
|
|
192
|
+
reject(
|
|
193
|
+
formatProcessFailure(
|
|
194
|
+
"Failed to send PluginInvokeRequest to child process.",
|
|
195
|
+
stderrChunks,
|
|
196
|
+
error,
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
launchPlan,
|
|
205
|
+
|
|
206
|
+
async invoke(request = {}) {
|
|
207
|
+
const requestBytes = encodePluginInvokeRequest(request);
|
|
208
|
+
const responseBytes = await invokeRaw(requestBytes);
|
|
209
|
+
return decodePluginInvokeResponse(responseBytes);
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
invokeRaw,
|
|
213
|
+
|
|
214
|
+
async destroy() {
|
|
215
|
+
expectedShutdown = true;
|
|
216
|
+
if (!closed) {
|
|
217
|
+
child.kill();
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
await closePromise;
|
|
221
|
+
} catch {
|
|
222
|
+
// Best-effort shutdown: callers only need pending requests cleared.
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import * as flatbuffers from "flatbuffers";
|
|
2
|
+
|
|
3
|
+
import { createRecipientKeypairHex, protectModuleArtifact } from "../compiler/compileModule.js";
|
|
4
|
+
import { extractPublicationRecordCollection } from "../transport/records.js";
|
|
5
|
+
import { REC } from "spacedatastandards.org/lib/js/REC/REC.js";
|
|
6
|
+
import { Record } from "spacedatastandards.org/lib/js/REC/Record.js";
|
|
7
|
+
import { PNM } from "spacedatastandards.org/lib/js/PNM/main.js";
|
|
8
|
+
import { ENC } from "spacedatastandards.org/lib/js/ENC/main.js";
|
|
9
|
+
|
|
10
|
+
const MINIMAL_WASM_BYTES = Uint8Array.of(
|
|
11
|
+
0x00,
|
|
12
|
+
0x61,
|
|
13
|
+
0x73,
|
|
14
|
+
0x6d,
|
|
15
|
+
0x01,
|
|
16
|
+
0x00,
|
|
17
|
+
0x00,
|
|
18
|
+
0x00,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export function createPublicationProtectionDemoManifest() {
|
|
22
|
+
return {
|
|
23
|
+
pluginId: "com.digitalarsenal.examples.publication-protection-demo",
|
|
24
|
+
name: "Publication Protection Demo",
|
|
25
|
+
version: "0.1.0",
|
|
26
|
+
pluginFamily: "propagator",
|
|
27
|
+
capabilities: ["clock", "crypto_sign", "crypto_encrypt"],
|
|
28
|
+
externalInterfaces: [],
|
|
29
|
+
methods: [
|
|
30
|
+
{
|
|
31
|
+
methodId: "propagate",
|
|
32
|
+
displayName: "Propagate",
|
|
33
|
+
inputPorts: [
|
|
34
|
+
{
|
|
35
|
+
portId: "request",
|
|
36
|
+
acceptedTypeSets: [
|
|
37
|
+
{
|
|
38
|
+
setId: "omm-request",
|
|
39
|
+
allowedTypes: [
|
|
40
|
+
{
|
|
41
|
+
schemaName: "OMM.fbs",
|
|
42
|
+
fileIdentifier: "$OMM",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
minStreams: 1,
|
|
48
|
+
maxStreams: 1,
|
|
49
|
+
required: true,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
outputPorts: [
|
|
53
|
+
{
|
|
54
|
+
portId: "state",
|
|
55
|
+
acceptedTypeSets: [
|
|
56
|
+
{
|
|
57
|
+
setId: "state-vector",
|
|
58
|
+
allowedTypes: [
|
|
59
|
+
{
|
|
60
|
+
schemaName: "StateVector.fbs",
|
|
61
|
+
fileIdentifier: "STVC",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
schemaName: "StateVector.fbs",
|
|
65
|
+
fileIdentifier: "STVC",
|
|
66
|
+
wireFormat: "aligned-binary",
|
|
67
|
+
rootTypeName: "StateVector",
|
|
68
|
+
byteLength: 72,
|
|
69
|
+
requiredAlignment: 8,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
minStreams: 1,
|
|
75
|
+
maxStreams: 1,
|
|
76
|
+
required: true,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
maxBatch: 32,
|
|
80
|
+
drainPolicy: "drain-to-empty",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
schemasUsed: [
|
|
84
|
+
{
|
|
85
|
+
schemaName: "OMM.fbs",
|
|
86
|
+
fileIdentifier: "$OMM",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
schemaName: "StateVector.fbs",
|
|
90
|
+
fileIdentifier: "STVC",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
schemaName: "StateVector.fbs",
|
|
94
|
+
fileIdentifier: "STVC",
|
|
95
|
+
wireFormat: "aligned-binary",
|
|
96
|
+
rootTypeName: "StateVector",
|
|
97
|
+
byteLength: 72,
|
|
98
|
+
requiredAlignment: 8,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function summarizeAlignedBinaryContract(manifest = {}) {
|
|
105
|
+
const summaries = [];
|
|
106
|
+
for (const method of Array.isArray(manifest.methods) ? manifest.methods : []) {
|
|
107
|
+
const ports = [
|
|
108
|
+
...(Array.isArray(method.inputPorts) ? method.inputPorts : []),
|
|
109
|
+
...(Array.isArray(method.outputPorts) ? method.outputPorts : []),
|
|
110
|
+
];
|
|
111
|
+
for (const port of ports) {
|
|
112
|
+
for (const typeSet of Array.isArray(port.acceptedTypeSets)
|
|
113
|
+
? port.acceptedTypeSets
|
|
114
|
+
: []) {
|
|
115
|
+
const allowedTypes = Array.isArray(typeSet.allowedTypes)
|
|
116
|
+
? typeSet.allowedTypes
|
|
117
|
+
: [];
|
|
118
|
+
for (const allowedType of allowedTypes) {
|
|
119
|
+
if (allowedType?.wireFormat !== "aligned-binary") {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const hasFlatbufferFallback = allowedTypes.some(
|
|
123
|
+
(candidate) =>
|
|
124
|
+
(candidate?.wireFormat ?? "flatbuffer") === "flatbuffer" &&
|
|
125
|
+
candidate?.schemaName === allowedType.schemaName &&
|
|
126
|
+
candidate?.fileIdentifier === allowedType.fileIdentifier,
|
|
127
|
+
);
|
|
128
|
+
summaries.push({
|
|
129
|
+
methodId: method.methodId ?? null,
|
|
130
|
+
portId: port.portId ?? null,
|
|
131
|
+
setId: typeSet.setId ?? null,
|
|
132
|
+
schemaName: allowedType.schemaName ?? null,
|
|
133
|
+
fileIdentifier: allowedType.fileIdentifier ?? null,
|
|
134
|
+
rootTypeName: allowedType.rootTypeName ?? null,
|
|
135
|
+
byteLength: allowedType.byteLength ?? null,
|
|
136
|
+
requiredAlignment: allowedType.requiredAlignment ?? null,
|
|
137
|
+
hasFlatbufferFallback,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return summaries;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseStandardsRec(recordCollectionBytes) {
|
|
147
|
+
const recBuffer = new flatbuffers.ByteBuffer(recordCollectionBytes);
|
|
148
|
+
const rec = REC.getRootAsREC(recBuffer);
|
|
149
|
+
const records = [];
|
|
150
|
+
for (let index = 0; index < rec.recordsLength(); index += 1) {
|
|
151
|
+
const record = rec.RECORDS(index, new Record());
|
|
152
|
+
if (!record) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const standard = record.standard() ?? null;
|
|
156
|
+
if (standard === "PNM") {
|
|
157
|
+
const pnm = record.value(new PNM());
|
|
158
|
+
records.push({
|
|
159
|
+
standard,
|
|
160
|
+
fileIdentifier: "$PNM",
|
|
161
|
+
fileName: pnm?.FILE_NAME() ?? null,
|
|
162
|
+
fileId: pnm?.FILE_ID() ?? null,
|
|
163
|
+
cid: pnm?.CID() ?? null,
|
|
164
|
+
hasSignature: Boolean(pnm?.SIGNATURE()),
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (standard === "ENC") {
|
|
169
|
+
const enc = record.value(new ENC());
|
|
170
|
+
records.push({
|
|
171
|
+
standard,
|
|
172
|
+
fileIdentifier: "$ENC",
|
|
173
|
+
context: enc?.CONTEXT() ?? null,
|
|
174
|
+
rootType: enc?.ROOT_TYPE() ?? null,
|
|
175
|
+
nonceLength: enc?.nonceStartLength() ?? 0,
|
|
176
|
+
ephemeralPublicKeyLength: enc?.ephemeralPublicKeyLength() ?? 0,
|
|
177
|
+
});
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
records.push({
|
|
181
|
+
standard,
|
|
182
|
+
fileIdentifier: null,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
fileIdentifier: "$REC",
|
|
187
|
+
version: rec.version() ?? null,
|
|
188
|
+
recordCount: records.length,
|
|
189
|
+
recordStandards: records.map((record) => record.standard),
|
|
190
|
+
usesStandardsFlatbuffers: REC.bufferHasIdentifier(
|
|
191
|
+
new flatbuffers.ByteBuffer(recordCollectionBytes),
|
|
192
|
+
),
|
|
193
|
+
records,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function summarizeProtectedArtifact(protectedArtifact) {
|
|
198
|
+
const parsed = extractPublicationRecordCollection(
|
|
199
|
+
protectedArtifact.protectedArtifactBytes,
|
|
200
|
+
);
|
|
201
|
+
if (!parsed) {
|
|
202
|
+
throw new Error("Protected artifact is missing the REC publication trailer.");
|
|
203
|
+
}
|
|
204
|
+
const trailer = parseStandardsRec(parsed.recordCollectionBytes);
|
|
205
|
+
return {
|
|
206
|
+
artifactId: protectedArtifact.payload.artifactId,
|
|
207
|
+
encrypted: protectedArtifact.encrypted,
|
|
208
|
+
trailer,
|
|
209
|
+
recordStandards: trailer.recordStandards,
|
|
210
|
+
pnm: parsed.pnm
|
|
211
|
+
? {
|
|
212
|
+
fileName: parsed.pnm.fileName ?? null,
|
|
213
|
+
fileId: parsed.pnm.fileId ?? null,
|
|
214
|
+
cid: parsed.pnm.cid ?? null,
|
|
215
|
+
hasSignature: Boolean(parsed.pnm.signature),
|
|
216
|
+
signatureType: parsed.pnm.signatureType ?? null,
|
|
217
|
+
publishTimestamp: parsed.pnm.publishTimestamp ?? null,
|
|
218
|
+
}
|
|
219
|
+
: null,
|
|
220
|
+
enc: parsed.enc
|
|
221
|
+
? {
|
|
222
|
+
context: parsed.enc.context ?? null,
|
|
223
|
+
rootType: parsed.enc.rootType ?? null,
|
|
224
|
+
keyExchange: parsed.enc.keyExchange ?? null,
|
|
225
|
+
symmetric: parsed.enc.symmetric ?? null,
|
|
226
|
+
keyDerivation: parsed.enc.keyDerivation ?? null,
|
|
227
|
+
nonceLength: parsed.enc.nonceStart?.length ?? 0,
|
|
228
|
+
ephemeralPublicKeyLength: parsed.enc.ephemeralPublicKey?.length ?? 0,
|
|
229
|
+
}
|
|
230
|
+
: null,
|
|
231
|
+
envelope: protectedArtifact.encryptedEnvelope
|
|
232
|
+
? {
|
|
233
|
+
scheme: protectedArtifact.encryptedEnvelope.scheme ?? null,
|
|
234
|
+
hasEncRecord: Boolean(
|
|
235
|
+
protectedArtifact.encryptedEnvelope.encRecordBase64,
|
|
236
|
+
),
|
|
237
|
+
hasPnmRecord: Boolean(
|
|
238
|
+
protectedArtifact.encryptedEnvelope.pnmRecordBase64,
|
|
239
|
+
),
|
|
240
|
+
}
|
|
241
|
+
: null,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function createPublicationProtectionDemoSummary(options = {}) {
|
|
246
|
+
const manifest = options.manifest ?? createPublicationProtectionDemoManifest();
|
|
247
|
+
const wasmBytes =
|
|
248
|
+
options.wasmBytes instanceof Uint8Array
|
|
249
|
+
? options.wasmBytes
|
|
250
|
+
: MINIMAL_WASM_BYTES;
|
|
251
|
+
const recipient = options.recipient ?? (await createRecipientKeypairHex());
|
|
252
|
+
const signedOnly = await protectModuleArtifact({
|
|
253
|
+
manifest,
|
|
254
|
+
wasmBytes,
|
|
255
|
+
mnemonic: options.mnemonic ?? null,
|
|
256
|
+
});
|
|
257
|
+
const encryptedDelivery = await protectModuleArtifact({
|
|
258
|
+
manifest,
|
|
259
|
+
wasmBytes,
|
|
260
|
+
mnemonic: options.mnemonic ?? null,
|
|
261
|
+
recipientPublicKeyHex: recipient.publicKeyHex,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
manifest,
|
|
266
|
+
recTrailer: parseStandardsRec(signedOnly.publicationRecordsBytes),
|
|
267
|
+
alignedBinaryContract: summarizeAlignedBinaryContract(manifest),
|
|
268
|
+
signedOnly: summarizeProtectedArtifact(signedOnly),
|
|
269
|
+
encryptedDelivery: summarizeProtectedArtifact(encryptedDelivery),
|
|
270
|
+
};
|
|
271
|
+
}
|
package/src/transport/records.js
CHANGED
|
@@ -277,7 +277,27 @@ function validateEncTable(table, buffer, label) {
|
|
|
277
277
|
maxLength: 32,
|
|
278
278
|
});
|
|
279
279
|
assertOptionalStringField(buffer, tableMeta, 22, `${label} root type`);
|
|
280
|
-
const
|
|
280
|
+
const timestamp = table.TIMESTAMP();
|
|
281
|
+
const record = {
|
|
282
|
+
version: Number(table.VERSION()),
|
|
283
|
+
keyExchange:
|
|
284
|
+
KEY_EXCHANGE_NAME_BY_VALUE[table.KEY_EXCHANGE()] ??
|
|
285
|
+
String(table.KEY_EXCHANGE()),
|
|
286
|
+
symmetric:
|
|
287
|
+
SYMMETRIC_ALGO_NAME_BY_VALUE[table.SYMMETRIC()] ??
|
|
288
|
+
String(table.SYMMETRIC()),
|
|
289
|
+
keyDerivation:
|
|
290
|
+
KDF_NAME_BY_VALUE[table.KEY_DERIVATION()] ??
|
|
291
|
+
String(table.KEY_DERIVATION()),
|
|
292
|
+
ephemeralPublicKey: normalizeByteField(table.ephemeralPublicKeyArray()),
|
|
293
|
+
nonceStart: normalizeByteField(table.nonceStartArray()),
|
|
294
|
+
recipientKeyId: normalizeByteField(table.recipientKeyIdArray()),
|
|
295
|
+
context: normalizeStringField(table.CONTEXT()),
|
|
296
|
+
schemaHash: normalizeByteField(table.schemaHashArray()),
|
|
297
|
+
rootType: normalizeStringField(table.ROOT_TYPE()),
|
|
298
|
+
timestamp:
|
|
299
|
+
timestamp === undefined || timestamp === null ? 0 : Number(timestamp),
|
|
300
|
+
};
|
|
281
301
|
if (!record.ephemeralPublicKey?.length) {
|
|
282
302
|
throw new Error(`${label} is missing the ephemeral public key.`);
|
|
283
303
|
}
|
|
@@ -316,7 +336,17 @@ function validatePnmTable(table, buffer, label) {
|
|
|
316
336
|
assertOptionalStringField(buffer, tableMeta, 16, `${label} timestamp signature`);
|
|
317
337
|
assertOptionalStringField(buffer, tableMeta, 18, `${label} signature type`);
|
|
318
338
|
assertOptionalStringField(buffer, tableMeta, 20, `${label} timestamp signature type`);
|
|
319
|
-
const record =
|
|
339
|
+
const record = {
|
|
340
|
+
multiformatAddress: normalizeStringField(table.MULTIFORMAT_ADDRESS()),
|
|
341
|
+
publishTimestamp: normalizeStringField(table.PUBLISH_TIMESTAMP()),
|
|
342
|
+
cid: normalizeStringField(table.CID()),
|
|
343
|
+
fileName: normalizeStringField(table.FILE_NAME()),
|
|
344
|
+
fileId: normalizeStringField(table.FILE_ID()),
|
|
345
|
+
signature: normalizeStringField(table.SIGNATURE()),
|
|
346
|
+
timestampSignature: normalizeStringField(table.TIMESTAMP_SIGNATURE()),
|
|
347
|
+
signatureType: normalizeStringField(table.SIGNATURE_TYPE()),
|
|
348
|
+
timestampSignatureType: normalizeStringField(table.TIMESTAMP_SIGNATURE_TYPE()),
|
|
349
|
+
};
|
|
320
350
|
if (
|
|
321
351
|
!record.multiformatAddress &&
|
|
322
352
|
!record.publishTimestamp &&
|
|
@@ -426,48 +456,6 @@ function pnmTableFromObject(record = {}) {
|
|
|
426
456
|
);
|
|
427
457
|
}
|
|
428
458
|
|
|
429
|
-
function normalizeEncTable(table) {
|
|
430
|
-
if (!table) {
|
|
431
|
-
return null;
|
|
432
|
-
}
|
|
433
|
-
return {
|
|
434
|
-
version: Number(table.VERSION ?? 1),
|
|
435
|
-
keyExchange:
|
|
436
|
-
KEY_EXCHANGE_NAME_BY_VALUE[table.KEY_EXCHANGE] ?? String(table.KEY_EXCHANGE),
|
|
437
|
-
symmetric:
|
|
438
|
-
SYMMETRIC_ALGO_NAME_BY_VALUE[table.SYMMETRIC] ?? String(table.SYMMETRIC),
|
|
439
|
-
keyDerivation:
|
|
440
|
-
KDF_NAME_BY_VALUE[table.KEY_DERIVATION] ?? String(table.KEY_DERIVATION),
|
|
441
|
-
ephemeralPublicKey: normalizeByteField(table.EPHEMERAL_PUBLIC_KEY),
|
|
442
|
-
nonceStart: normalizeByteField(table.NONCE_START),
|
|
443
|
-
recipientKeyId: normalizeByteField(table.RECIPIENT_KEY_ID),
|
|
444
|
-
context: normalizeStringField(table.CONTEXT),
|
|
445
|
-
schemaHash: normalizeByteField(table.SCHEMA_HASH),
|
|
446
|
-
rootType: normalizeStringField(table.ROOT_TYPE),
|
|
447
|
-
timestamp:
|
|
448
|
-
table.TIMESTAMP === undefined || table.TIMESTAMP === null
|
|
449
|
-
? 0
|
|
450
|
-
: Number(table.TIMESTAMP),
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function normalizePnmTable(table) {
|
|
455
|
-
if (!table) {
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
return {
|
|
459
|
-
multiformatAddress: normalizeStringField(table.MULTIFORMAT_ADDRESS),
|
|
460
|
-
publishTimestamp: normalizeStringField(table.PUBLISH_TIMESTAMP),
|
|
461
|
-
cid: normalizeStringField(table.CID),
|
|
462
|
-
fileName: normalizeStringField(table.FILE_NAME),
|
|
463
|
-
fileId: normalizeStringField(table.FILE_ID),
|
|
464
|
-
signature: normalizeStringField(table.SIGNATURE),
|
|
465
|
-
timestampSignature: normalizeStringField(table.TIMESTAMP_SIGNATURE),
|
|
466
|
-
signatureType: normalizeStringField(table.SIGNATURE_TYPE),
|
|
467
|
-
timestampSignatureType: normalizeStringField(table.TIMESTAMP_SIGNATURE_TYPE),
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
|
|
471
459
|
function readFooterLength(bytes) {
|
|
472
460
|
const view = toUint8Array(bytes);
|
|
473
461
|
if (view.length < TRAILER_FOOTER_LENGTH) {
|
|
@@ -601,7 +589,6 @@ export function decodePublicationRecordCollection(bytes) {
|
|
|
601
589
|
if (recordTables.length === 0) {
|
|
602
590
|
throw new Error("REC trailer does not contain any records.");
|
|
603
591
|
}
|
|
604
|
-
const collection = collectionTable.unpack();
|
|
605
592
|
const records = [];
|
|
606
593
|
let enc = null;
|
|
607
594
|
let pnm = null;
|
|
@@ -622,10 +609,12 @@ export function decodePublicationRecordCollection(bytes) {
|
|
|
622
609
|
8,
|
|
623
610
|
`REC trailer record ${index} standard`,
|
|
624
611
|
);
|
|
625
|
-
const
|
|
626
|
-
|
|
612
|
+
const recordType =
|
|
613
|
+
typeof recordTable.valueType === "function"
|
|
614
|
+
? recordTable.valueType()
|
|
615
|
+
: recordTable.value_type();
|
|
627
616
|
const standard =
|
|
628
|
-
normalizeStringField(
|
|
617
|
+
normalizeStringField(recordTable.standard()) ??
|
|
629
618
|
STANDARD_BY_RECORD_TYPE[recordType] ??
|
|
630
619
|
null;
|
|
631
620
|
const expectedStandard =
|
|
@@ -644,10 +633,7 @@ export function decodePublicationRecordCollection(bytes) {
|
|
|
644
633
|
if (!valueMeta) {
|
|
645
634
|
throw new Error(`REC trailer record ${index} is missing a value.`);
|
|
646
635
|
}
|
|
647
|
-
let value =
|
|
648
|
-
standard === "ENC" || standard === "PNM"
|
|
649
|
-
? null
|
|
650
|
-
: unpackedRecord?.value ?? null;
|
|
636
|
+
let value = null;
|
|
651
637
|
if (standard === "ENC") {
|
|
652
638
|
const encTable = recordTable.value(new ENC());
|
|
653
639
|
if (!encTable) {
|
|
@@ -684,7 +670,9 @@ export function decodePublicationRecordCollection(bytes) {
|
|
|
684
670
|
});
|
|
685
671
|
}
|
|
686
672
|
return {
|
|
687
|
-
version:
|
|
673
|
+
version:
|
|
674
|
+
normalizeStringField(collectionTable.version()) ??
|
|
675
|
+
DEFAULT_RECORD_COLLECTION_VERSION,
|
|
688
676
|
records,
|
|
689
677
|
enc,
|
|
690
678
|
pnm,
|