@ai-ecoverse/slicc-kernel 1.171.0 → 1.172.1
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 +23 -7
- package/dist/driver-worker.js +4 -2
- package/dist/driver-worker.js.map +2 -2
- package/dist/index.js +223 -37
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +507 -196
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +4 -2
- package/dist/node-driver-worker.js.map +2 -2
- package/dist/node-process-worker.cjs +1 -1
- package/dist/node-process-worker.js +1 -1
- package/dist/node.js +462 -233
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +1 -1
- package/dist/testing.js +498 -3
- package/dist/testing.js.map +4 -4
- package/dist/types/fs/attest.d.ts +12 -2
- package/dist/types/index.d.ts +12 -4
- package/dist/types/kernel/net/http-syscalls.d.ts +3 -1
- package/dist/types/kernel/net/loopback-transport.d.ts +1 -1
- package/dist/types/kernel/net/placeholder-floor.d.ts +9 -3
- package/dist/types/kernel/net/remote-transport.d.ts +4 -1
- package/dist/types/kernel/net/sealed-fetch.d.ts +27 -0
- package/dist/types/kernel/net/transport.d.ts +2 -1
- package/dist/types/kernel/socket.d.ts +4 -1
- package/dist/types/launcher.d.ts +12 -2
- package/dist/types/node.d.ts +12 -4
- package/dist/types/sealed-relay.d.ts +12 -2
- package/dist/types/serve.d.ts +8 -1
- package/dist/types/testing.d.ts +3 -0
- package/dist/types/transport.d.ts +2 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -598,6 +598,54 @@ function isHostname(name) {
|
|
|
598
598
|
return name.split(".").every((l) => LABEL.test(l));
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
+
// src/body-idle.ts
|
|
602
|
+
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
603
|
+
function idleFailure(ms) {
|
|
604
|
+
return Object.assign(new Error(`the response body sent no data for ${ms} ms`), {
|
|
605
|
+
code: "ETIMEDOUT"
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
async function readWithin(reader, ms) {
|
|
609
|
+
if (!(ms > 0 && ms < Number.POSITIVE_INFINITY)) return reader.read();
|
|
610
|
+
let timer;
|
|
611
|
+
const idle = new Promise((_, reject) => {
|
|
612
|
+
timer = setTimeout(() => reject(idleFailure(ms)), ms);
|
|
613
|
+
});
|
|
614
|
+
try {
|
|
615
|
+
return await Promise.race([reader.read(), idle]);
|
|
616
|
+
} finally {
|
|
617
|
+
clearTimeout(timer);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/kernel/net/sealed-fetch.ts
|
|
622
|
+
var SEALED_FETCH_VERSION = 1;
|
|
623
|
+
var UNSAFE_FIELD = /[\r\n\0]/;
|
|
624
|
+
function sealedHooks(option) {
|
|
625
|
+
if (!option) return {};
|
|
626
|
+
if (typeof option === "function") return { request: option };
|
|
627
|
+
const { request, fetch } = option;
|
|
628
|
+
if (typeof request !== "function" || fetch !== void 0 && typeof fetch !== "function") {
|
|
629
|
+
throw new TypeError("sealed takes a function or { request, fetch? }");
|
|
630
|
+
}
|
|
631
|
+
return { request, ...fetch ? { fetch } : {} };
|
|
632
|
+
}
|
|
633
|
+
function headerList(headers) {
|
|
634
|
+
return Array.isArray(headers) && headers.every(
|
|
635
|
+
(field) => Array.isArray(field) && field.length === 2 && field.every((text) => typeof text === "string" && !UNSAFE_FIELD.test(text))
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
function sealedReplyValid(reply) {
|
|
639
|
+
if (typeof reply !== "object" || reply === null) return false;
|
|
640
|
+
const { redirects, redirected, status, statusText, headers, url, body, cancel } = reply;
|
|
641
|
+
return redirects === void 0 && redirected !== true && Number.isInteger(status) && status >= 100 && status <= 599 && typeof statusText === "string" && !UNSAFE_FIELD.test(statusText) && headerList(headers) && (url === void 0 || typeof url === "string") && typeof body?.[Symbol.asyncIterator] === "function" && typeof cancel === "function";
|
|
642
|
+
}
|
|
643
|
+
function dropSealedReply(reply) {
|
|
644
|
+
const { cancel } = reply ?? {};
|
|
645
|
+
if (typeof cancel !== "function") return;
|
|
646
|
+
void Promise.resolve().then(() => cancel.call(reply)).catch(() => void 0);
|
|
647
|
+
}
|
|
648
|
+
|
|
601
649
|
// src/kernel/net/routes.ts
|
|
602
650
|
var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
|
|
603
651
|
function ipv4Bytes(text) {
|
|
@@ -917,8 +965,129 @@ var KERNEL_SIGINFO = Object.freeze({
|
|
|
917
965
|
});
|
|
918
966
|
|
|
919
967
|
// src/sealed-relay.ts
|
|
920
|
-
function
|
|
968
|
+
function textOf(err) {
|
|
969
|
+
return err instanceof Error ? err.message : String(err);
|
|
970
|
+
}
|
|
971
|
+
function sendBytes(port, bytes) {
|
|
972
|
+
const copy = fresh(bytes);
|
|
973
|
+
port.postMessage({ t: "chunk", bytes: copy }, [copy.buffer]);
|
|
974
|
+
}
|
|
975
|
+
function uploadOf(far) {
|
|
976
|
+
let waiting;
|
|
977
|
+
let failure2;
|
|
978
|
+
const next = () => {
|
|
979
|
+
if (failure2) return Promise.reject(failure2);
|
|
980
|
+
waiting = Promise.withResolvers();
|
|
981
|
+
far.postMessage({ t: "pull" });
|
|
982
|
+
return waiting.promise;
|
|
983
|
+
};
|
|
984
|
+
return {
|
|
985
|
+
stream: {
|
|
986
|
+
[Symbol.asyncIterator]: () => ({
|
|
987
|
+
next,
|
|
988
|
+
return: async () => {
|
|
989
|
+
far.postMessage({ t: "stop" });
|
|
990
|
+
return { value: void 0, done: true };
|
|
991
|
+
}
|
|
992
|
+
})
|
|
993
|
+
},
|
|
994
|
+
answer(message) {
|
|
995
|
+
if (message.t === "chunk") waiting?.resolve({ value: message.bytes, done: false });
|
|
996
|
+
else if (message.t === "end") waiting?.resolve({ value: void 0, done: true });
|
|
997
|
+
else {
|
|
998
|
+
failure2 = new Error(message.message);
|
|
999
|
+
waiting?.reject(failure2);
|
|
1000
|
+
}
|
|
1001
|
+
waiting = void 0;
|
|
1002
|
+
},
|
|
1003
|
+
fail(reason) {
|
|
1004
|
+
failure2 = reason;
|
|
1005
|
+
waiting?.reject(reason);
|
|
1006
|
+
waiting = void 0;
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
function answerFetch(hook, { req, port: far }, live) {
|
|
1011
|
+
const control = new AbortController();
|
|
1012
|
+
const { streamed, ...fields } = req;
|
|
1013
|
+
const upload = uploadOf(far);
|
|
1014
|
+
let source;
|
|
1015
|
+
let reply;
|
|
1016
|
+
let closed = false;
|
|
1017
|
+
const settle = () => {
|
|
1018
|
+
closed = true;
|
|
1019
|
+
live.delete(answer);
|
|
1020
|
+
upload.fail(new Error("the request ended"));
|
|
1021
|
+
};
|
|
1022
|
+
const answer = {
|
|
1023
|
+
close(reason) {
|
|
1024
|
+
if (closed) return;
|
|
1025
|
+
settle();
|
|
1026
|
+
control.abort(reason);
|
|
1027
|
+
dropSealedReply(reply);
|
|
1028
|
+
far.close();
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
const finish = (message) => {
|
|
1032
|
+
if (closed) return;
|
|
1033
|
+
settle();
|
|
1034
|
+
far.postMessage(message);
|
|
1035
|
+
};
|
|
1036
|
+
const failWith = (err) => {
|
|
1037
|
+
dropSealedReply(reply);
|
|
1038
|
+
finish({ t: "error", message: textOf(err) });
|
|
1039
|
+
};
|
|
1040
|
+
const read = async () => {
|
|
1041
|
+
try {
|
|
1042
|
+
const next = await source.next();
|
|
1043
|
+
if (closed) return;
|
|
1044
|
+
if (next.done) finish({ t: "end" });
|
|
1045
|
+
else if (next.value instanceof Uint8Array) sendBytes(far, next.value);
|
|
1046
|
+
else failWith("a body chunk is not bytes");
|
|
1047
|
+
} catch (err) {
|
|
1048
|
+
failWith(err);
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
const ask = async () => {
|
|
1052
|
+
try {
|
|
1053
|
+
if (!hook) throw new Error("no vault is connected");
|
|
1054
|
+
const request = {
|
|
1055
|
+
...fields,
|
|
1056
|
+
...streamed ? { bodyStream: upload.stream } : {}
|
|
1057
|
+
};
|
|
1058
|
+
const answered = await hook(request, { signal: control.signal });
|
|
1059
|
+
if (!sealedReplyValid(answered)) {
|
|
1060
|
+
dropSealedReply(answered);
|
|
1061
|
+
throw new Error("the reply is not a scrubbed response");
|
|
1062
|
+
}
|
|
1063
|
+
if (closed) return dropSealedReply(answered);
|
|
1064
|
+
reply = answered;
|
|
1065
|
+
source = answered.body[Symbol.asyncIterator]();
|
|
1066
|
+
const { status, statusText, headers, url } = answered;
|
|
1067
|
+
far.postMessage({
|
|
1068
|
+
t: "head",
|
|
1069
|
+
status,
|
|
1070
|
+
statusText,
|
|
1071
|
+
headers,
|
|
1072
|
+
...url !== void 0 ? { url } : {}
|
|
1073
|
+
});
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
failWith(err);
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
far.addEventListener("message", ({ data }) => {
|
|
1079
|
+
if (data.t === "read") void read();
|
|
1080
|
+
else if (data.t === "cancel") answer.close(data.reason);
|
|
1081
|
+
else upload.answer(data);
|
|
1082
|
+
});
|
|
1083
|
+
far.addEventListener("close", () => answer.close("withdrawn"));
|
|
1084
|
+
far.start();
|
|
1085
|
+
live.add(answer);
|
|
1086
|
+
void ask();
|
|
1087
|
+
}
|
|
1088
|
+
function sealedAnswerer(port, hook, fetchHook) {
|
|
921
1089
|
const asked = /* @__PURE__ */ new Map();
|
|
1090
|
+
const fetches = /* @__PURE__ */ new Set();
|
|
922
1091
|
const ask = async ({ id, req }) => {
|
|
923
1092
|
const control = new AbortController();
|
|
924
1093
|
asked.set(id, control);
|
|
@@ -942,36 +1111,18 @@ function sealedAnswerer(port, hook) {
|
|
|
942
1111
|
const message = data;
|
|
943
1112
|
if (message?.sealedAsk) void ask(message.sealedAsk);
|
|
944
1113
|
else if (message?.sealedAbort) abort(message.sealedAbort);
|
|
1114
|
+
else if (message?.sealedFetch) answerFetch(fetchHook, message.sealedFetch, fetches);
|
|
945
1115
|
else return false;
|
|
946
1116
|
return true;
|
|
947
1117
|
},
|
|
948
1118
|
close() {
|
|
949
1119
|
for (const control of asked.values()) control.abort("withdrawn");
|
|
950
1120
|
asked.clear();
|
|
1121
|
+
for (const answer of [...fetches]) answer.close("withdrawn");
|
|
951
1122
|
}
|
|
952
1123
|
};
|
|
953
1124
|
}
|
|
954
1125
|
|
|
955
|
-
// src/body-idle.ts
|
|
956
|
-
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
957
|
-
function idleFailure(ms) {
|
|
958
|
-
return Object.assign(new Error(`the response body sent no data for ${ms} ms`), {
|
|
959
|
-
code: "ETIMEDOUT"
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
async function readWithin(reader, ms) {
|
|
963
|
-
if (!(ms > 0 && ms < Number.POSITIVE_INFINITY)) return reader.read();
|
|
964
|
-
let timer;
|
|
965
|
-
const idle = new Promise((_, reject) => {
|
|
966
|
-
timer = setTimeout(() => reject(idleFailure(ms)), ms);
|
|
967
|
-
});
|
|
968
|
-
try {
|
|
969
|
-
return await Promise.race([reader.read(), idle]);
|
|
970
|
-
} finally {
|
|
971
|
-
clearTimeout(timer);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
|
|
975
1126
|
// src/kernel/net/ws-queue.ts
|
|
976
1127
|
var INBOUND_LIMIT = 16 * 1024 * 1024;
|
|
977
1128
|
var MESSAGE_LIMIT = 64 * 1024 * 1024;
|
|
@@ -1163,6 +1314,11 @@ async function* chunks(body, idleMs) {
|
|
|
1163
1314
|
await reader.cancel().catch(() => void 0);
|
|
1164
1315
|
}
|
|
1165
1316
|
}
|
|
1317
|
+
function sentHeaders(headers, inPage) {
|
|
1318
|
+
const out = joinHeaders(headers);
|
|
1319
|
+
if (inPage) out.delete("user-agent");
|
|
1320
|
+
return out;
|
|
1321
|
+
}
|
|
1166
1322
|
function streamOf(body) {
|
|
1167
1323
|
const it = body[Symbol.asyncIterator]();
|
|
1168
1324
|
return new ReadableStream(
|
|
@@ -1190,6 +1346,7 @@ function fetchTransport(options = {}) {
|
|
|
1190
1346
|
const Socket = options.webSocket === true ? globalThis.WebSocket : options.webSocket || void 0;
|
|
1191
1347
|
const headers = options.webSocketHeaders === true;
|
|
1192
1348
|
const instance = options.redirect ?? "follow";
|
|
1349
|
+
const inPage = !options.fetch && "location" in globalThis;
|
|
1193
1350
|
return {
|
|
1194
1351
|
traits: {
|
|
1195
1352
|
manualRedirects: instance === "manual",
|
|
@@ -1206,7 +1363,7 @@ function fetchTransport(options = {}) {
|
|
|
1206
1363
|
try {
|
|
1207
1364
|
response = await send(request.url, {
|
|
1208
1365
|
method: request.method,
|
|
1209
|
-
headers:
|
|
1366
|
+
headers: sentHeaders(request.headers, inPage),
|
|
1210
1367
|
...requestBody(request),
|
|
1211
1368
|
signal: request.signal,
|
|
1212
1369
|
redirect: request.redirect ?? instance,
|
|
@@ -1282,7 +1439,21 @@ function headOf(nid, response) {
|
|
|
1282
1439
|
headers: response.headers
|
|
1283
1440
|
};
|
|
1284
1441
|
}
|
|
1285
|
-
function
|
|
1442
|
+
function requestOf(call, signal, uploads) {
|
|
1443
|
+
const { url, method, headers, streamed, body, redirect, nid } = call;
|
|
1444
|
+
return {
|
|
1445
|
+
url,
|
|
1446
|
+
method,
|
|
1447
|
+
headers,
|
|
1448
|
+
...streamed ? { bodyStream: uploads.of(nid, signal) } : body ? { body } : {},
|
|
1449
|
+
...redirect !== void 0 ? { redirect } : {},
|
|
1450
|
+
signal
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
function serveTransport(peer, transport, {
|
|
1454
|
+
closeWaitMs = CLOSE_WAIT_MS,
|
|
1455
|
+
attributed = false
|
|
1456
|
+
} = {}) {
|
|
1286
1457
|
const open = /* @__PURE__ */ new Map();
|
|
1287
1458
|
const fail = (nid, e) => {
|
|
1288
1459
|
open.delete(nid);
|
|
@@ -1302,14 +1473,11 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
1302
1473
|
const entry = { abort };
|
|
1303
1474
|
open.set(call.nid, entry);
|
|
1304
1475
|
try {
|
|
1305
|
-
const response = await transport.fetch(
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
...call.redirect !== void 0 ? { redirect: call.redirect } : {},
|
|
1311
|
-
signal: abort.signal
|
|
1312
|
-
});
|
|
1476
|
+
const response = await transport.fetch(
|
|
1477
|
+
requestOf(call, abort.signal, uploads),
|
|
1478
|
+
attributed ? call.owner : void 0,
|
|
1479
|
+
attributed ? call.stamp : void 0
|
|
1480
|
+
);
|
|
1313
1481
|
if (!open.has(call.nid)) {
|
|
1314
1482
|
await response.cancel();
|
|
1315
1483
|
return;
|
|
@@ -1564,7 +1732,7 @@ var RemoteTransport = class {
|
|
|
1564
1732
|
waiter?.reject(new TransportError("the request was cancelled"));
|
|
1565
1733
|
this.port.postMessage({ net: "cancel", nid });
|
|
1566
1734
|
}
|
|
1567
|
-
async fetch(request) {
|
|
1735
|
+
async fetch(request, owner, stamp) {
|
|
1568
1736
|
const nid = ++this.nextId;
|
|
1569
1737
|
const { signal, redirect } = request;
|
|
1570
1738
|
signal.throwIfAborted();
|
|
@@ -1594,7 +1762,9 @@ var RemoteTransport = class {
|
|
|
1594
1762
|
method: request.method,
|
|
1595
1763
|
headers: request.headers,
|
|
1596
1764
|
...request.bodyStream ? { streamed: true } : request.body ? { body: request.body } : {},
|
|
1597
|
-
...redirect !== void 0 ? { redirect } : {}
|
|
1765
|
+
...redirect !== void 0 ? { redirect } : {},
|
|
1766
|
+
...owner !== void 0 ? { owner } : {},
|
|
1767
|
+
...stamp ? { stamp } : {}
|
|
1598
1768
|
});
|
|
1599
1769
|
} catch (e) {
|
|
1600
1770
|
finish();
|
|
@@ -2279,6 +2449,7 @@ function uplinkInit(uplink) {
|
|
|
2279
2449
|
return { uplink: { traits, ...uplink.routes ? { routes: uplink.routes } : {}, ...nameserver } };
|
|
2280
2450
|
}
|
|
2281
2451
|
function initCall(options, transport) {
|
|
2452
|
+
const hooks = sealedHooks(options.sealed);
|
|
2282
2453
|
return {
|
|
2283
2454
|
op: "init",
|
|
2284
2455
|
...options.root ? { root: options.root } : {},
|
|
@@ -2297,7 +2468,8 @@ function initCall(options, transport) {
|
|
|
2297
2468
|
...options.hostname !== void 0 ? { hostname: options.hostname } : {},
|
|
2298
2469
|
users: options.users === void 0 ? DEFAULT_USERS : options.users,
|
|
2299
2470
|
...options.onDenial ? { denials: true } : {},
|
|
2300
|
-
...
|
|
2471
|
+
...hooks.request ? { sealed: true } : {},
|
|
2472
|
+
...hooks.fetch ? { sealedFetch: true } : {},
|
|
2301
2473
|
...options.clipboard ? { clipboard: true } : {},
|
|
2302
2474
|
...options.requests ? { requests: kindsOf(options.requests) } : {},
|
|
2303
2475
|
...options.approve ? { approve: true } : {},
|
|
@@ -2458,6 +2630,7 @@ function checkOptions(options) {
|
|
|
2458
2630
|
if (options.protect) protectInit(options.protect);
|
|
2459
2631
|
integrityOptions(options.protect?.integrity);
|
|
2460
2632
|
facadesInit(options.facades);
|
|
2633
|
+
sealedHooks(options.sealed);
|
|
2461
2634
|
}
|
|
2462
2635
|
function connector(call, bound) {
|
|
2463
2636
|
return async (connectOptions = {}) => {
|
|
@@ -2474,7 +2647,8 @@ function connector(call, bound) {
|
|
|
2474
2647
|
};
|
|
2475
2648
|
}
|
|
2476
2649
|
function hookRelays(worker, options, bound) {
|
|
2477
|
-
const
|
|
2650
|
+
const hooks = sealedHooks(options.sealed);
|
|
2651
|
+
const sealed = sealedAnswerer(worker, hooks.request, hooks.fetch);
|
|
2478
2652
|
const clipboard = clipboardAnswerer(worker, options.clipboard);
|
|
2479
2653
|
const integrity = integrityAnswerer(worker, options.protect?.integrity?.lookup);
|
|
2480
2654
|
const requests = embedderAnswerer(
|
|
@@ -2509,6 +2683,17 @@ function traitsUpdater(transport, call) {
|
|
|
2509
2683
|
return await call({ op: "transport-traits", ...traits }) === true;
|
|
2510
2684
|
};
|
|
2511
2685
|
}
|
|
2686
|
+
function transportBridge(worker, transport) {
|
|
2687
|
+
return transport ? serveTransport(worker, transport, { attributed: true }) : void 0;
|
|
2688
|
+
}
|
|
2689
|
+
function pageOnly(call) {
|
|
2690
|
+
return {
|
|
2691
|
+
sealed: {
|
|
2692
|
+
setPlaceholders: async (list) => void await call({ op: "sealed-placeholders", list })
|
|
2693
|
+
},
|
|
2694
|
+
attest: ((path, options) => call({ op: "attest", path, ...options ? { options } : {} }))
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2512
2697
|
async function createKernel(options = {}) {
|
|
2513
2698
|
checkOptions(options);
|
|
2514
2699
|
const url = options.worker ?? new URL("./kernel-worker.js", import.meta.url);
|
|
@@ -2539,7 +2724,7 @@ async function createKernel(options = {}) {
|
|
|
2539
2724
|
pending.clear();
|
|
2540
2725
|
};
|
|
2541
2726
|
const transport = options.network?.transport;
|
|
2542
|
-
const bridge =
|
|
2727
|
+
const bridge = transportBridge(worker, transport);
|
|
2543
2728
|
const uplinks = uplinkServer(worker, options.network?.uplink);
|
|
2544
2729
|
async function chosen(handle) {
|
|
2545
2730
|
if (handle) {
|
|
@@ -2663,7 +2848,7 @@ async function createKernel(options = {}) {
|
|
|
2663
2848
|
umount: async (target) => void await call({ op: "umount", target }),
|
|
2664
2849
|
mounts: async () => await call({ op: "mounts" }),
|
|
2665
2850
|
insert: async (target, handle) => void await call({ op: "insert", target, handle }),
|
|
2666
|
-
|
|
2851
|
+
...pageOnly(call),
|
|
2667
2852
|
watch: files.watch,
|
|
2668
2853
|
terminate() {
|
|
2669
2854
|
fail(new Error("slicc-kernel terminated"));
|
|
@@ -2674,6 +2859,7 @@ async function createKernel(options = {}) {
|
|
|
2674
2859
|
export {
|
|
2675
2860
|
KernelCallError,
|
|
2676
2861
|
KernelGoneError,
|
|
2862
|
+
SEALED_FETCH_VERSION,
|
|
2677
2863
|
attachKernel,
|
|
2678
2864
|
createKernel,
|
|
2679
2865
|
fetchTransport,
|