nervur 0.22.0 → 0.22.2-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORING.md +741 -0
- package/KIT-SPEC.md +286 -0
- package/LICENSE +1 -1
- package/NOTICE +6 -2
- package/README.md +97 -424
- package/dist/app/app-ground.d.ts +18 -0
- package/dist/app/app-ground.js +25 -0
- package/dist/app/index.d.ts +2 -0
- package/dist/app/index.js +5 -0
- package/dist/app/native.d.ts +46 -0
- package/dist/app/native.js +101 -0
- package/dist/being/being.d.ts +229 -0
- package/dist/being/being.js +20 -0
- package/dist/being/covers.d.ts +9 -0
- package/dist/being/covers.js +21 -0
- package/dist/being/index.d.ts +3 -0
- package/dist/being/index.js +5 -0
- package/dist/being/need.d.ts +49 -0
- package/dist/being/need.js +33 -0
- package/dist/being/schema.d.ts +100 -0
- package/dist/being/schema.js +132 -0
- package/dist/being/table.d.ts +42 -0
- package/dist/being/table.js +288 -0
- package/dist/bench/bench-ground.d.ts +55 -0
- package/dist/bench/bench-ground.js +101 -0
- package/dist/bench/bench.d.ts +87 -0
- package/dist/bench/bench.js +260 -0
- package/dist/bench/fake-carry.d.ts +32 -0
- package/dist/bench/fake-carry.js +56 -0
- package/dist/bench/fake-clock.d.ts +17 -0
- package/dist/bench/fake-clock.js +34 -0
- package/dist/bench/fake-custody.d.ts +9 -0
- package/dist/bench/fake-custody.js +10 -0
- package/dist/bench/fake-faculty.d.ts +25 -0
- package/dist/bench/fake-faculty.js +48 -0
- package/dist/bench/fake-keys.d.ts +5 -0
- package/dist/bench/fake-keys.js +9 -0
- package/dist/bench/fake-memory.d.ts +14 -0
- package/dist/bench/fake-memory.js +46 -0
- package/dist/bench/fake-network.d.ts +57 -0
- package/dist/bench/fake-network.js +169 -0
- package/dist/bench/index.d.ts +11 -0
- package/dist/bench/index.js +13 -0
- package/dist/bench/seeded.d.ts +11 -0
- package/dist/bench/seeded.js +37 -0
- package/dist/bench/settle.d.ts +2 -0
- package/dist/bench/settle.js +49 -0
- package/dist/bench/stewards.d.ts +17 -0
- package/dist/bench/stewards.js +20 -0
- package/dist/bodies/class-list.d.ts +14 -0
- package/dist/bodies/class-list.js +33 -0
- package/dist/bodies/joined-carry.d.ts +23 -0
- package/dist/bodies/joined-carry.js +42 -0
- package/dist/bodies/noble-crypto.d.ts +22 -0
- package/dist/bodies/noble-crypto.js +88 -0
- package/dist/bodies/seed-keys.d.ts +10 -0
- package/dist/bodies/seed-keys.js +42 -0
- package/dist/bodies/strict-tools.d.ts +10 -0
- package/dist/bodies/strict-tools.js +306 -0
- package/dist/bodies/web-carry.d.ts +65 -0
- package/dist/bodies/web-carry.js +224 -0
- package/dist/bodies/web-clock.d.ts +12 -0
- package/dist/bodies/web-clock.js +33 -0
- package/dist/browser/browser-ground.d.ts +69 -0
- package/dist/browser/browser-ground.js +219 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +7 -0
- package/dist/browser/indexeddb-memory.d.ts +17 -0
- package/dist/browser/indexeddb-memory.js +97 -0
- package/dist/browser/locked-custody.d.ts +25 -0
- package/dist/browser/locked-custody.js +89 -0
- package/dist/browser/origin-classes.d.ts +7 -0
- package/dist/browser/origin-classes.js +31 -0
- package/dist/foundation.d.ts +150 -0
- package/dist/foundation.js +4 -0
- package/dist/ground/ground.d.ts +177 -0
- package/dist/ground/ground.js +363 -0
- package/dist/house/crossing.d.ts +37 -0
- package/dist/house/crossing.js +131 -0
- package/dist/house/house.d.ts +80 -0
- package/dist/house/house.js +1753 -0
- package/dist/house/rows-shape.d.ts +103 -0
- package/dist/house/rows-shape.js +12 -0
- package/dist/house/rows.d.ts +27 -0
- package/dist/house/rows.js +168 -0
- package/dist/index.d.ts +29 -1
- package/dist/index.js +25 -14
- package/dist/node/bridge.d.ts +17 -0
- package/dist/node/bridge.js +188 -0
- package/dist/node/cli.js +170 -0
- package/dist/node/custody.d.ts +20 -0
- package/dist/node/custody.js +34 -0
- package/dist/node/file-keys.d.ts +7 -0
- package/dist/node/file-keys.js +30 -0
- package/dist/node/file-memory.d.ts +13 -0
- package/dist/node/file-memory.js +91 -0
- package/dist/node/folder-classes.d.ts +5 -0
- package/dist/node/folder-classes.js +40 -0
- package/dist/node/gone.d.ts +2 -0
- package/dist/node/gone.js +7 -0
- package/dist/node/hand.d.ts +29 -0
- package/dist/node/hand.js +85 -0
- package/dist/node/held-keys.d.ts +14 -0
- package/dist/node/held-keys.js +36 -0
- package/dist/node/http.d.ts +11 -0
- package/dist/node/http.js +90 -0
- package/dist/node/index.d.ts +13 -0
- package/dist/node/index.js +15 -0
- package/dist/node/keychain-keys.d.ts +10 -0
- package/dist/node/keychain-keys.js +51 -0
- package/dist/node/ledger-memory.d.ts +17 -0
- package/dist/node/ledger-memory.js +151 -0
- package/dist/node/lock.d.ts +10 -0
- package/dist/node/lock.js +78 -0
- package/dist/node/node-ground.d.ts +33 -0
- package/dist/node/node-ground.js +147 -0
- package/dist/node/notify.d.ts +1 -0
- package/dist/node/notify.js +18 -0
- package/dist/node/tcp-carry.d.ts +39 -0
- package/dist/node/tcp-carry.js +216 -0
- package/dist/node/websocket.d.ts +6 -0
- package/dist/node/websocket.js +118 -0
- package/dist/quo/frame.d.ts +20 -0
- package/dist/quo/frame.js +48 -0
- package/dist/quo/room.d.ts +154 -0
- package/dist/quo/room.js +362 -0
- package/dist/serve/index.d.ts +27 -0
- package/dist/serve/index.js +55 -0
- package/package.json +49 -47
- package/dist/browser.d.ts +0 -1
- package/dist/browser.js +0 -8
- package/dist/cli.js +0 -16
- package/dist/core/being/being.d.ts +0 -17
- package/dist/core/being/being.js +0 -72
- package/dist/core/being/digest.d.ts +0 -3
- package/dist/core/being/digest.js +0 -17
- package/dist/core/being/faculty.d.ts +0 -10
- package/dist/core/being/faculty.js +0 -77
- package/dist/core/being/index.d.ts +0 -6
- package/dist/core/being/index.js +0 -8
- package/dist/core/being/kind.d.ts +0 -6
- package/dist/core/being/kind.js +0 -34
- package/dist/core/being/types.d.ts +0 -61
- package/dist/core/being/types.js +0 -4
- package/dist/core/being/words.d.ts +0 -13
- package/dist/core/being/words.js +0 -14
- package/dist/core/browser/index.d.ts +0 -37
- package/dist/core/browser/index.js +0 -196
- package/dist/core/browser/worker.d.ts +0 -11
- package/dist/core/browser/worker.js +0 -37
- package/dist/core/cli/command.d.ts +0 -13
- package/dist/core/cli/command.js +0 -330
- package/dist/core/cli/edge.d.ts +0 -7
- package/dist/core/cli/edge.js +0 -79
- package/dist/core/cli/harbor.d.ts +0 -25
- package/dist/core/cli/harbor.js +0 -115
- package/dist/core/contract/index.d.ts +0 -42
- package/dist/core/contract/index.js +0 -34
- package/dist/core/contract/link.d.ts +0 -4
- package/dist/core/contract/link.js +0 -45
- package/dist/core/crypto/aes.d.ts +0 -3
- package/dist/core/crypto/aes.js +0 -24
- package/dist/core/crypto/bytes.d.ts +0 -6
- package/dist/core/crypto/bytes.js +0 -33
- package/dist/core/crypto/ed25519.d.ts +0 -3
- package/dist/core/crypto/ed25519.js +0 -93
- package/dist/core/crypto/hash.d.ts +0 -2
- package/dist/core/crypto/hash.js +0 -11
- package/dist/core/crypto/index.d.ts +0 -7
- package/dist/core/crypto/index.js +0 -10
- package/dist/core/crypto/json.d.ts +0 -8
- package/dist/core/crypto/json.js +0 -250
- package/dist/core/crypto/mlkem.d.ts +0 -12
- package/dist/core/crypto/mlkem.js +0 -36
- package/dist/core/crypto/subtle.d.ts +0 -3
- package/dist/core/crypto/subtle.js +0 -11
- package/dist/core/crypto/x25519.d.ts +0 -2
- package/dist/core/crypto/x25519.js +0 -24
- package/dist/core/edge/index.d.ts +0 -164
- package/dist/core/edge/index.js +0 -739
- package/dist/core/edge/kit.d.ts +0 -2
- package/dist/core/edge/kit.js +0 -2
- package/dist/core/folder/index.d.ts +0 -18
- package/dist/core/folder/index.js +0 -130
- package/dist/core/git/http.d.ts +0 -6
- package/dist/core/git/http.js +0 -102
- package/dist/core/git/index.d.ts +0 -5
- package/dist/core/git/index.js +0 -10
- package/dist/core/git/object.d.ts +0 -40
- package/dist/core/git/object.js +0 -103
- package/dist/core/git/pack.d.ts +0 -9
- package/dist/core/git/pack.js +0 -171
- package/dist/core/git/store.d.ts +0 -23
- package/dist/core/git/store.js +0 -120
- package/dist/core/git/zlib.d.ts +0 -6
- package/dist/core/git/zlib.js +0 -210
- package/dist/core/harbor/carrying.d.ts +0 -51
- package/dist/core/harbor/carrying.js +0 -69
- package/dist/core/harbor/catalogue.d.ts +0 -33
- package/dist/core/harbor/catalogue.js +0 -365
- package/dist/core/harbor/dna.d.ts +0 -13
- package/dist/core/harbor/dna.js +0 -120
- package/dist/core/harbor/dock.d.ts +0 -62
- package/dist/core/harbor/dock.js +0 -381
- package/dist/core/harbor/harbor.d.ts +0 -16
- package/dist/core/harbor/harbor.js +0 -438
- package/dist/core/harbor/index.d.ts +0 -11
- package/dist/core/harbor/index.js +0 -14
- package/dist/core/harbor/memory.d.ts +0 -10
- package/dist/core/harbor/memory.js +0 -25
- package/dist/core/harbor/package.d.ts +0 -20
- package/dist/core/harbor/package.js +0 -172
- package/dist/core/harbor/probe.d.ts +0 -22
- package/dist/core/harbor/probe.js +0 -15
- package/dist/core/harbor/registry.d.ts +0 -15
- package/dist/core/harbor/registry.js +0 -110
- package/dist/core/harbor/root-line.d.ts +0 -7
- package/dist/core/harbor/root-line.js +0 -9
- package/dist/core/harbor/terrain.d.ts +0 -25
- package/dist/core/harbor/terrain.js +0 -28
- package/dist/core/http/index.d.ts +0 -2
- package/dist/core/http/index.js +0 -154
- package/dist/core/http/websocket.d.ts +0 -29
- package/dist/core/http/websocket.js +0 -133
- package/dist/core/line/answer.d.ts +0 -8
- package/dist/core/line/answer.js +0 -60
- package/dist/core/line/frame.d.ts +0 -29
- package/dist/core/line/frame.js +0 -77
- package/dist/core/line/ground.d.ts +0 -3
- package/dist/core/line/ground.js +0 -7
- package/dist/core/line/index.d.ts +0 -4
- package/dist/core/line/index.js +0 -8
- package/dist/core/line/web.d.ts +0 -12
- package/dist/core/line/web.js +0 -117
- package/dist/core/node/index.d.ts +0 -23
- package/dist/core/node/index.js +0 -124
- package/dist/core/pointer/bodies.d.ts +0 -31
- package/dist/core/pointer/bodies.js +0 -118
- package/dist/core/pointer/index.d.ts +0 -2
- package/dist/core/pointer/index.js +0 -4
- package/dist/core/pointer/world.d.ts +0 -45
- package/dist/core/pointer/world.js +0 -106
- package/dist/core/proof/contracts.d.ts +0 -19
- package/dist/core/proof/contracts.js +0 -116
- package/dist/core/proof/expect.d.ts +0 -6
- package/dist/core/proof/expect.js +0 -18
- package/dist/core/proof/index.d.ts +0 -3
- package/dist/core/proof/index.js +0 -14
- package/dist/core/proof/law.d.ts +0 -25
- package/dist/core/proof/law.js +0 -715
- package/dist/core/quo/address.d.ts +0 -19
- package/dist/core/quo/address.js +0 -84
- package/dist/core/quo/door.d.ts +0 -34
- package/dist/core/quo/door.js +0 -174
- package/dist/core/quo/index.d.ts +0 -9
- package/dist/core/quo/index.js +0 -12
- package/dist/core/quo/invitation.d.ts +0 -8
- package/dist/core/quo/invitation.js +0 -19
- package/dist/core/quo/keys.d.ts +0 -40
- package/dist/core/quo/keys.js +0 -79
- package/dist/core/quo/payload.d.ts +0 -15
- package/dist/core/quo/payload.js +0 -55
- package/dist/core/quo/relations.d.ts +0 -40
- package/dist/core/quo/relations.js +0 -33
- package/dist/core/quo/reply.d.ts +0 -13
- package/dist/core/quo/reply.js +0 -35
- package/dist/core/quo/seal.d.ts +0 -42
- package/dist/core/quo/seal.js +0 -78
- package/dist/core/quo/standing.d.ts +0 -39
- package/dist/core/quo/standing.js +0 -90
- package/dist/core/tcp/index.d.ts +0 -27
- package/dist/core/tcp/index.js +0 -209
- package/dist/core/ward/allowance.d.ts +0 -14
- package/dist/core/ward/allowance.js +0 -32
- package/dist/core/ward/cells.d.ts +0 -7
- package/dist/core/ward/cells.js +0 -74
- package/dist/core/ward/house.d.ts +0 -67
- package/dist/core/ward/house.js +0 -473
- package/dist/core/ward/index.d.ts +0 -5
- package/dist/core/ward/index.js +0 -7
- package/dist/core/ward/partition.d.ts +0 -50
- package/dist/core/ward/partition.js +0 -66
- package/dist/core/ward/stance.d.ts +0 -29
- package/dist/core/ward/stance.js +0 -186
- package/dist/core/ward/ward.d.ts +0 -44
- package/dist/core/ward/ward.js +0 -140
- package/dist/core.d.ts +0 -10
- package/dist/core.js +0 -19
- package/dist/defaults/dock/index.d.ts +0 -4
- package/dist/defaults/dock/index.js +0 -11
- package/dist/defaults/index.d.ts +0 -5
- package/dist/defaults/index.js +0 -10
- package/dist/defaults/memory/index.d.ts +0 -12
- package/dist/defaults/memory/index.js +0 -39
- package/dist/defaults/tcp/index.d.ts +0 -16
- package/dist/defaults/tcp/index.js +0 -82
- package/dist/defaults/ward/index.d.ts +0 -4
- package/dist/defaults/ward/index.js +0 -11
- package/dist/defaults/web/index.d.ts +0 -15
- package/dist/defaults/web/index.js +0 -90
- package/dist/edge.d.ts +0 -1
- package/dist/edge.js +0 -9
- package/dist/kit.d.ts +0 -4
- package/dist/kit.js +0 -12
- package/dist/node.d.ts +0 -1
- package/dist/node.js +0 -8
- package/src/browser.ts +0 -10
- package/src/cli.ts +0 -15
- package/src/core/being/being.ts +0 -84
- package/src/core/being/digest.ts +0 -18
- package/src/core/being/faculty.ts +0 -76
- package/src/core/being/index.ts +0 -8
- package/src/core/being/kind.ts +0 -37
- package/src/core/being/types.ts +0 -74
- package/src/core/being/words.ts +0 -31
- package/src/core/browser/index.ts +0 -219
- package/src/core/browser/worker.ts +0 -63
- package/src/core/cli/command.ts +0 -311
- package/src/core/cli/edge.ts +0 -86
- package/src/core/cli/harbor.ts +0 -120
- package/src/core/contract/index.ts +0 -82
- package/src/core/contract/link.ts +0 -58
- package/src/core/crypto/aes.ts +0 -26
- package/src/core/crypto/bytes.ts +0 -37
- package/src/core/crypto/ed25519.ts +0 -92
- package/src/core/crypto/hash.ts +0 -14
- package/src/core/crypto/index.ts +0 -10
- package/src/core/crypto/json.ts +0 -241
- package/src/core/crypto/mlkem.ts +0 -38
- package/src/core/crypto/subtle.ts +0 -13
- package/src/core/crypto/x25519.ts +0 -23
- package/src/core/edge/index.ts +0 -865
- package/src/core/folder/index.ts +0 -137
- package/src/core/git/http.ts +0 -103
- package/src/core/git/index.ts +0 -10
- package/src/core/git/object.ts +0 -116
- package/src/core/git/pack.ts +0 -147
- package/src/core/git/store.ts +0 -122
- package/src/core/git/zlib.ts +0 -197
- package/src/core/harbor/carrying.ts +0 -124
- package/src/core/harbor/catalogue.ts +0 -386
- package/src/core/harbor/dna.ts +0 -116
- package/src/core/harbor/dock.ts +0 -420
- package/src/core/harbor/harbor.ts +0 -433
- package/src/core/harbor/index.ts +0 -14
- package/src/core/harbor/memory.ts +0 -31
- package/src/core/harbor/package.ts +0 -195
- package/src/core/harbor/probe.ts +0 -39
- package/src/core/harbor/registry.ts +0 -107
- package/src/core/harbor/root-line.ts +0 -19
- package/src/core/harbor/terrain.ts +0 -54
- package/src/core/http/index.ts +0 -147
- package/src/core/http/websocket.ts +0 -124
- package/src/core/line/answer.ts +0 -60
- package/src/core/line/frame.ts +0 -83
- package/src/core/line/ground.ts +0 -18
- package/src/core/line/index.ts +0 -8
- package/src/core/line/web.ts +0 -123
- package/src/core/node/index.ts +0 -139
- package/src/core/pointer/bodies.ts +0 -122
- package/src/core/pointer/index.ts +0 -4
- package/src/core/pointer/world.ts +0 -134
- package/src/core/proof/contracts.ts +0 -134
- package/src/core/proof/expect.ts +0 -25
- package/src/core/proof/index.ts +0 -14
- package/src/core/proof/law.ts +0 -742
- package/src/core/quo/address.ts +0 -87
- package/src/core/quo/door.ts +0 -180
- package/src/core/quo/index.ts +0 -12
- package/src/core/quo/invitation.ts +0 -19
- package/src/core/quo/keys.ts +0 -91
- package/src/core/quo/payload.ts +0 -61
- package/src/core/quo/relations.ts +0 -62
- package/src/core/quo/reply.ts +0 -38
- package/src/core/quo/seal.ts +0 -101
- package/src/core/quo/standing.ts +0 -111
- package/src/core/stand/main.ts +0 -20
- package/src/core/stand/stand.ts +0 -259
- package/src/core/tcp/index.ts +0 -227
- package/src/core/ward/allowance.ts +0 -47
- package/src/core/ward/cells.ts +0 -77
- package/src/core/ward/house.ts +0 -524
- package/src/core/ward/index.ts +0 -7
- package/src/core/ward/partition.ts +0 -117
- package/src/core/ward/stance.ts +0 -207
- package/src/core/ward/ward.ts +0 -166
- package/src/core.ts +0 -55
- package/src/defaults/dock/index.ts +0 -12
- package/src/defaults/index.ts +0 -10
- package/src/defaults/memory/index.ts +0 -50
- package/src/defaults/tcp/index.ts +0 -89
- package/src/defaults/ward/index.ts +0 -12
- package/src/defaults/web/index.ts +0 -97
- package/src/edge.ts +0 -11
- package/src/index.ts +0 -18
- package/src/kit.ts +0 -15
- package/src/node.ts +0 -10
- /package/dist/{cli.d.ts → node/cli.d.ts} +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Hooked } from '../ground/ground.ts';
|
|
2
|
+
import type { Clock } from '../foundation.ts';
|
|
3
|
+
/** How one link behaves, each way: its latency in milliseconds, and the share of boxes dropped, of replies lost, and of boxes delivered twice. */
|
|
4
|
+
export interface Link {
|
|
5
|
+
readonly latency?: number;
|
|
6
|
+
readonly drop?: number;
|
|
7
|
+
readonly lose?: number;
|
|
8
|
+
readonly duplicate?: number;
|
|
9
|
+
}
|
|
10
|
+
/** What became of one box: dropped before its door, delivered, its reply lost, or delivered twice. */
|
|
11
|
+
export interface Crossing {
|
|
12
|
+
readonly from: string;
|
|
13
|
+
readonly to: string;
|
|
14
|
+
readonly ward: string;
|
|
15
|
+
readonly outcome: 'dropped' | 'answered' | 'silent' | 'lost' | 'twice';
|
|
16
|
+
}
|
|
17
|
+
export declare class FakeNetwork {
|
|
18
|
+
#private;
|
|
19
|
+
/** Every box that crossed between two hosts, in order. A box by pointer inside a host crosses nothing. */
|
|
20
|
+
readonly crossings: Crossing[];
|
|
21
|
+
/** `clock` is the bench's, and times every latency; `seed` draws every chance. */
|
|
22
|
+
constructor({ clock, seed }?: {
|
|
23
|
+
clock?: Clock;
|
|
24
|
+
seed?: string;
|
|
25
|
+
});
|
|
26
|
+
/** A host's carry. One that listens is dialled at its names; one that does not only dials, as a device. */
|
|
27
|
+
join(host: string, { names, listens }?: {
|
|
28
|
+
names?: readonly string[];
|
|
29
|
+
listens?: boolean;
|
|
30
|
+
}): Hooked;
|
|
31
|
+
/** A name pointed at a host, as a DNS record moved. */
|
|
32
|
+
point(name: string, host: string): void;
|
|
33
|
+
/** How the link between two hosts behaves, both ways, until it is set again. */
|
|
34
|
+
link(a: string, b: string, link: Link): void;
|
|
35
|
+
/** No box crosses from `from` to `to` until `heal`; the other way stands. */
|
|
36
|
+
cut(from: string, to: string): void;
|
|
37
|
+
/** No box crosses between the two hosts, either way, until `heal`. */
|
|
38
|
+
partition(a: string, b: string): void;
|
|
39
|
+
/** Every cut mended. Links keep how they behave. */
|
|
40
|
+
heal(): void;
|
|
41
|
+
/** The host answers nothing, its doors forgotten, until `up`. */
|
|
42
|
+
down(host: string): void;
|
|
43
|
+
up(host: string): void;
|
|
44
|
+
/** The next `count` boxes between hosts are dropped before their door. */
|
|
45
|
+
dropNext(count?: number): void;
|
|
46
|
+
/** The next `count` replies are lost after the far door answered. */
|
|
47
|
+
loseNext(count?: number): void;
|
|
48
|
+
/** The next `count` boxes are delivered twice, and the first reply returns. */
|
|
49
|
+
duplicateNext(count?: number): void;
|
|
50
|
+
/**
|
|
51
|
+
* The clock moved on by `ms` in steps of `step`, the bench settling after
|
|
52
|
+
* each, so latencies end, retries leave and replies land as time passes.
|
|
53
|
+
*/
|
|
54
|
+
elapse(ms: number, { step }?: {
|
|
55
|
+
step?: number;
|
|
56
|
+
}): Promise<void>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { stream } from './seeded.js';
|
|
2
|
+
import { settle } from './settle.js';
|
|
3
|
+
const SCHEME = 'bench://';
|
|
4
|
+
const pair = (a, b) => [a, b].sort().join('\n');
|
|
5
|
+
export class FakeNetwork {
|
|
6
|
+
#hosts = new Map();
|
|
7
|
+
#names = new Map();
|
|
8
|
+
// One-way cuts, `from` then `to`.
|
|
9
|
+
#cuts = new Set();
|
|
10
|
+
#links = new Map();
|
|
11
|
+
#clock;
|
|
12
|
+
#chance;
|
|
13
|
+
#drop = 0;
|
|
14
|
+
#lose = 0;
|
|
15
|
+
#twice = 0;
|
|
16
|
+
#waits = 0;
|
|
17
|
+
/** Every box that crossed between two hosts, in order. A box by pointer inside a host crosses nothing. */
|
|
18
|
+
crossings = [];
|
|
19
|
+
/** `clock` is the bench's, and times every latency; `seed` draws every chance. */
|
|
20
|
+
constructor({ clock, seed = 'network' } = {}) {
|
|
21
|
+
this.#clock = clock;
|
|
22
|
+
this.#chance = stream(`network:${seed}`);
|
|
23
|
+
}
|
|
24
|
+
/** A host's carry. One that listens is dialled at its names; one that does not only dials, as a device. */
|
|
25
|
+
join(host, { names = [], listens = true } = {}) {
|
|
26
|
+
const held = this.#hosts.get(host) ?? { doors: new Map(), listens, up: true };
|
|
27
|
+
this.#hosts.set(host, held);
|
|
28
|
+
for (const name of names)
|
|
29
|
+
this.#names.set(name, host);
|
|
30
|
+
return {
|
|
31
|
+
listen: ({ ward, door }) => void held.doors.set(ward, door),
|
|
32
|
+
unlisten: ({ ward }) => void held.doors.delete(ward),
|
|
33
|
+
send: (options) => this.#send(host, options),
|
|
34
|
+
at: () => (held.listens ? [...this.#names].filter(([, to]) => to === host).map(([name]) => SCHEME + name) : []),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** A name pointed at a host, as a DNS record moved. */
|
|
38
|
+
point(name, host) {
|
|
39
|
+
this.#names.set(name, host);
|
|
40
|
+
}
|
|
41
|
+
/** How the link between two hosts behaves, both ways, until it is set again. */
|
|
42
|
+
link(a, b, link) {
|
|
43
|
+
if (link.latency !== undefined && link.latency > 0 && this.#clock === undefined)
|
|
44
|
+
throw new TypeError('a network with latency is made with the bench’s clock');
|
|
45
|
+
for (const share of [link.drop, link.lose, link.duplicate])
|
|
46
|
+
if (share !== undefined && !(share >= 0 && share <= 1))
|
|
47
|
+
throw new TypeError('a share is between 0 and 1');
|
|
48
|
+
this.#links.set(pair(a, b), link);
|
|
49
|
+
}
|
|
50
|
+
/** No box crosses from `from` to `to` until `heal`; the other way stands. */
|
|
51
|
+
cut(from, to) {
|
|
52
|
+
this.#cuts.add(`${from}\n${to}`);
|
|
53
|
+
}
|
|
54
|
+
/** No box crosses between the two hosts, either way, until `heal`. */
|
|
55
|
+
partition(a, b) {
|
|
56
|
+
this.cut(a, b);
|
|
57
|
+
this.cut(b, a);
|
|
58
|
+
}
|
|
59
|
+
/** Every cut mended. Links keep how they behave. */
|
|
60
|
+
heal() {
|
|
61
|
+
this.#cuts.clear();
|
|
62
|
+
}
|
|
63
|
+
/** The host answers nothing, its doors forgotten, until `up`. */
|
|
64
|
+
down(host) {
|
|
65
|
+
const held = this.#hosts.get(host);
|
|
66
|
+
if (held === undefined)
|
|
67
|
+
return;
|
|
68
|
+
held.up = false;
|
|
69
|
+
held.doors.clear();
|
|
70
|
+
}
|
|
71
|
+
up(host) {
|
|
72
|
+
const held = this.#hosts.get(host);
|
|
73
|
+
if (held !== undefined)
|
|
74
|
+
held.up = true;
|
|
75
|
+
}
|
|
76
|
+
/** The next `count` boxes between hosts are dropped before their door. */
|
|
77
|
+
dropNext(count = 1) {
|
|
78
|
+
this.#drop += count;
|
|
79
|
+
}
|
|
80
|
+
/** The next `count` replies are lost after the far door answered. */
|
|
81
|
+
loseNext(count = 1) {
|
|
82
|
+
this.#lose += count;
|
|
83
|
+
}
|
|
84
|
+
/** The next `count` boxes are delivered twice, and the first reply returns. */
|
|
85
|
+
duplicateNext(count = 1) {
|
|
86
|
+
this.#twice += count;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The clock moved on by `ms` in steps of `step`, the bench settling after
|
|
90
|
+
* each, so latencies end, retries leave and replies land as time passes.
|
|
91
|
+
*/
|
|
92
|
+
async elapse(ms, { step = 1_000 } = {}) {
|
|
93
|
+
const clock = this.#clock;
|
|
94
|
+
if (clock === undefined || !('advance' in clock) || typeof clock.advance !== 'function')
|
|
95
|
+
throw new TypeError('a network elapses on the bench’s clock');
|
|
96
|
+
const advance = clock.advance.bind(clock);
|
|
97
|
+
await settle();
|
|
98
|
+
for (let left = ms; left > 0; left -= step) {
|
|
99
|
+
advance(Math.min(step, left));
|
|
100
|
+
await settle();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Whether a chance of `share` comes up, drawn from the seed.
|
|
104
|
+
#roll(share) {
|
|
105
|
+
if (share === undefined || share <= 0)
|
|
106
|
+
return false;
|
|
107
|
+
const [a, b] = this.#chance(2);
|
|
108
|
+
return (a * 256 + b) / 65_536 < share;
|
|
109
|
+
}
|
|
110
|
+
async #delay(ms) {
|
|
111
|
+
if (ms === undefined || ms <= 0 || this.#clock === undefined)
|
|
112
|
+
return;
|
|
113
|
+
await this.#clock.wait({ id: `network:${++this.#waits}`, ms });
|
|
114
|
+
}
|
|
115
|
+
async #send(from, { ward, at, box }) {
|
|
116
|
+
const own = this.#hosts.get(from);
|
|
117
|
+
if (own === undefined || !own.up)
|
|
118
|
+
return { reply: null, heard: false };
|
|
119
|
+
// A ward on this host takes the box by pointer, before any address, and crosses nothing.
|
|
120
|
+
const near = own.doors.get(ward);
|
|
121
|
+
if (near !== undefined) {
|
|
122
|
+
const reply = await near(box);
|
|
123
|
+
if (reply !== null)
|
|
124
|
+
return { reply };
|
|
125
|
+
}
|
|
126
|
+
for (const address of at) {
|
|
127
|
+
if (!address.startsWith(SCHEME))
|
|
128
|
+
continue;
|
|
129
|
+
const to = this.#names.get(address.slice(SCHEME.length));
|
|
130
|
+
const host = to === undefined ? undefined : this.#hosts.get(to);
|
|
131
|
+
if (to === undefined || host === undefined || !host.up || !host.listens || this.#cuts.has(`${from}\n${to}`))
|
|
132
|
+
continue;
|
|
133
|
+
const link = this.#links.get(pair(from, to)) ?? {};
|
|
134
|
+
const log = (outcome) => this.crossings.push({ from, to, ward, outcome });
|
|
135
|
+
await this.#delay(link.latency);
|
|
136
|
+
if (this.#drop > 0 || this.#roll(link.drop)) {
|
|
137
|
+
if (this.#drop > 0)
|
|
138
|
+
this.#drop--;
|
|
139
|
+
log('dropped');
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// The door is read again after the latency: a host may have gone down, or a cut come, meanwhile.
|
|
143
|
+
const door = host.up && !this.#cuts.has(`${from}\n${to}`) ? host.doors.get(ward) : undefined;
|
|
144
|
+
if (door === undefined)
|
|
145
|
+
continue;
|
|
146
|
+
const twice = this.#twice > 0 || this.#roll(link.duplicate);
|
|
147
|
+
if (this.#twice > 0)
|
|
148
|
+
this.#twice--;
|
|
149
|
+
const reply = await door(box);
|
|
150
|
+
if (twice)
|
|
151
|
+
await door(box);
|
|
152
|
+
await this.#delay(link.latency);
|
|
153
|
+
// A reply lost after the door answered was heard, so no other address is tried.
|
|
154
|
+
if (this.#lose > 0 || this.#roll(link.lose) || this.#cuts.has(`${to}\n${from}`)) {
|
|
155
|
+
if (this.#lose > 0)
|
|
156
|
+
this.#lose--;
|
|
157
|
+
log('lost');
|
|
158
|
+
return { reply: null, heard: true };
|
|
159
|
+
}
|
|
160
|
+
if (reply === null) {
|
|
161
|
+
log('silent');
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
log(twice ? 'twice' : 'answered');
|
|
165
|
+
return { reply, via: address };
|
|
166
|
+
}
|
|
167
|
+
return { reply: null, heard: false };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { Bench, Placed, type BenchOptions, type CheckOptions, type Finding } from './bench.ts';
|
|
2
|
+
export { BenchGround, Machine, type BenchGroundOptions, type HouseModule } from './bench-ground.ts';
|
|
3
|
+
export { FakeNetwork, type Crossing, type Link } from './fake-network.ts';
|
|
4
|
+
export { settle } from './settle.ts';
|
|
5
|
+
export { FakeFaculty, type Called } from './fake-faculty.ts';
|
|
6
|
+
export { FakeCarry } from './fake-carry.ts';
|
|
7
|
+
export { FakeCustody } from './fake-custody.ts';
|
|
8
|
+
export { FakeClock } from './fake-clock.ts';
|
|
9
|
+
export { FakeKeys } from './fake-keys.ts';
|
|
10
|
+
export { FakeMemory } from './fake-memory.ts';
|
|
11
|
+
export { covers } from '../being/covers.ts';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// `nervur/bench`: what a test imports to hold a class, a faculty or a body.
|
|
3
|
+
export { Bench, Placed } from './bench.js';
|
|
4
|
+
export { BenchGround, Machine } from './bench-ground.js';
|
|
5
|
+
export { FakeNetwork } from './fake-network.js';
|
|
6
|
+
export { settle } from './settle.js';
|
|
7
|
+
export { FakeFaculty } from './fake-faculty.js';
|
|
8
|
+
export { FakeCarry } from './fake-carry.js';
|
|
9
|
+
export { FakeCustody } from './fake-custody.js';
|
|
10
|
+
export { FakeClock } from './fake-clock.js';
|
|
11
|
+
export { FakeKeys } from './fake-keys.js';
|
|
12
|
+
export { FakeMemory } from './fake-memory.js';
|
|
13
|
+
export { covers } from '../being/covers.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NobleCrypto } from '../bodies/noble-crypto.ts';
|
|
2
|
+
/** A stream of bytes from a name: the same name, the same bytes. */
|
|
3
|
+
export declare const stream: (name: string) => (length: number) => Uint8Array;
|
|
4
|
+
/** Sixty-four hex digits from a name. */
|
|
5
|
+
export declare const seedOf: (name: string) => string;
|
|
6
|
+
/** The default crypto, with randomness drawn from a seed. */
|
|
7
|
+
export declare class SeededCrypto extends NobleCrypto {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(name: string);
|
|
10
|
+
random(length: number): Uint8Array;
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// What makes a bench run the same twice: bytes drawn from a seed, never
|
|
3
|
+
// from the platform. They are for tests alone and guard nothing.
|
|
4
|
+
import { NobleCrypto } from '../bodies/noble-crypto.js';
|
|
5
|
+
/** A stream of bytes from a name: the same name, the same bytes. */
|
|
6
|
+
export const stream = (name) => {
|
|
7
|
+
// FNV-1a over the name seeds xorshift32.
|
|
8
|
+
let state = 0x811c9dc5;
|
|
9
|
+
for (let at = 0; at < name.length; at++)
|
|
10
|
+
state = Math.imul(state ^ name.charCodeAt(at), 0x01000193) >>> 0;
|
|
11
|
+
if (state === 0)
|
|
12
|
+
state = 1;
|
|
13
|
+
return (length) => {
|
|
14
|
+
const out = new Uint8Array(length);
|
|
15
|
+
for (let at = 0; at < length; at++) {
|
|
16
|
+
state ^= state << 13;
|
|
17
|
+
state ^= state >>> 17;
|
|
18
|
+
state ^= state << 5;
|
|
19
|
+
state >>>= 0;
|
|
20
|
+
out[at] = state & 0xff;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** Sixty-four hex digits from a name. */
|
|
26
|
+
export const seedOf = (name) => Array.from(stream(`seed:${name}`)(32), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
27
|
+
/** The default crypto, with randomness drawn from a seed. */
|
|
28
|
+
export class SeededCrypto extends NobleCrypto {
|
|
29
|
+
#draw;
|
|
30
|
+
constructor(name) {
|
|
31
|
+
super();
|
|
32
|
+
this.#draw = stream(`random:${name}`);
|
|
33
|
+
}
|
|
34
|
+
random(length) {
|
|
35
|
+
return this.#draw(length);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Letting the bench go idle. The fakes do no I/O, so every ask, effect and
|
|
3
|
+
// reply they started runs on the event loop, and the one thing that leaves
|
|
4
|
+
// it is WebCrypto, which runs on the engine's own threads and outlasts any
|
|
5
|
+
// count of turns on a loaded machine. So the bench counts WebCrypto calls
|
|
6
|
+
// in flight, at `crypto.subtle` itself, the one door every body's crypto
|
|
7
|
+
// passes, and calls the loop idle only once none has been in flight for a
|
|
8
|
+
// run of turns. It counts in the process that imports the bench alone.
|
|
9
|
+
// Turns with nothing in flight before the bench calls the loop idle: the
|
|
10
|
+
// promise chains between two crypto calls resolve well within them.
|
|
11
|
+
const QUIET = 20;
|
|
12
|
+
// A loop that never goes idle is a fault the bench names, never a hang.
|
|
13
|
+
const BOUND = 200_000;
|
|
14
|
+
let inFlight = 0;
|
|
15
|
+
const METHODS = ['digest', 'importKey', 'exportKey', 'deriveBits', 'deriveKey', 'encrypt', 'decrypt', 'sign', 'verify', 'generateKey', 'wrapKey', 'unwrapKey'];
|
|
16
|
+
// Each method of the engine's `subtle`, counted while its promise is open. Wrapped once, however often the bench loads.
|
|
17
|
+
const subtle = globalThis.crypto?.subtle;
|
|
18
|
+
const COUNTED = Symbol.for('nervur.bench.counted');
|
|
19
|
+
if (subtle !== undefined && subtle[COUNTED] !== true) {
|
|
20
|
+
for (const method of METHODS) {
|
|
21
|
+
const original = subtle[method];
|
|
22
|
+
if (typeof original !== 'function')
|
|
23
|
+
continue;
|
|
24
|
+
subtle[method] = (...args) => {
|
|
25
|
+
inFlight++;
|
|
26
|
+
const done = () => {
|
|
27
|
+
inFlight--;
|
|
28
|
+
};
|
|
29
|
+
const called = original.apply(globalThis.crypto.subtle, args);
|
|
30
|
+
void called.then(done, done);
|
|
31
|
+
return called;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
subtle[COUNTED] = true;
|
|
35
|
+
}
|
|
36
|
+
// One turn of the event loop: an immediate where the engine has one, else the shortest timer.
|
|
37
|
+
const turnOnce = typeof globalThis.setImmediate === 'function'
|
|
38
|
+
? (next) => globalThis.setImmediate(next)
|
|
39
|
+
: (next) => setTimeout(next, 0);
|
|
40
|
+
/** Every ask, effect and reply the fakes started, run to where it waits on the clock or ends. */
|
|
41
|
+
export const settle = async () => {
|
|
42
|
+
let quiet = 0;
|
|
43
|
+
for (let turn = 0; quiet < QUIET; turn++) {
|
|
44
|
+
if (turn >= BOUND)
|
|
45
|
+
throw new Error('the bench never went idle: crypto stayed in flight');
|
|
46
|
+
await new Promise((next) => turnOnce(next));
|
|
47
|
+
quiet = inFlight === 0 ? quiet + 1 : 0;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Args } from '../being/index.ts';
|
|
2
|
+
declare const BenchSteward_base: import("../being/being.ts").BeingClass<Record<string, never>, Record<never, never>, {}>;
|
|
3
|
+
export declare class BenchSteward extends BenchSteward_base {
|
|
4
|
+
}
|
|
5
|
+
declare const BenchTester_base: import("../being/being.ts").BeingClass<Record<string, never>, Record<never, never>, {
|
|
6
|
+
readonly adopt: {
|
|
7
|
+
readonly for: "root";
|
|
8
|
+
readonly args: import("../being/schema.ts").Schema<{
|
|
9
|
+
invitation: import("../being/schema.ts").HandleMark;
|
|
10
|
+
}>;
|
|
11
|
+
readonly result: import("../being/schema.ts").Schema<string>;
|
|
12
|
+
};
|
|
13
|
+
}>;
|
|
14
|
+
export declare class BenchTester extends BenchTester_base {
|
|
15
|
+
adopt({ invitation }: Args<BenchTester, 'adopt'>): string;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The two stewards the bench stands on: one keeps the author's house where
|
|
3
|
+
// the test brings no steward of its own, the other adopts a handle to each
|
|
4
|
+
// being the bench places, in the bench's own house.
|
|
5
|
+
import { Being, s } from '../being/index.js';
|
|
6
|
+
export class BenchSteward extends Being.of({
|
|
7
|
+
kind: 'org.nervur.bench.steward',
|
|
8
|
+
asks: {},
|
|
9
|
+
}) {
|
|
10
|
+
}
|
|
11
|
+
export class BenchTester extends Being.of({
|
|
12
|
+
kind: 'org.nervur.bench.tester',
|
|
13
|
+
asks: {
|
|
14
|
+
adopt: { for: 'root', args: s.object({ invitation: s.handle() }), result: s.string() },
|
|
15
|
+
},
|
|
16
|
+
}) {
|
|
17
|
+
adopt({ invitation }) {
|
|
18
|
+
return invitation;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BeingClass, Classes } from '../foundation.ts';
|
|
2
|
+
export declare class ClassList implements Classes {
|
|
3
|
+
#private;
|
|
4
|
+
constructor({ steward, public: open, beings }: {
|
|
5
|
+
steward: BeingClass;
|
|
6
|
+
public?: BeingClass;
|
|
7
|
+
beings?: readonly BeingClass[];
|
|
8
|
+
});
|
|
9
|
+
resolve({ kind }: {
|
|
10
|
+
kind: string;
|
|
11
|
+
}): Promise<BeingClass | undefined>;
|
|
12
|
+
steward(): string;
|
|
13
|
+
public(): string | undefined;
|
|
14
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Classes from a plain list, each kind from the one class that declares it.
|
|
3
|
+
import { declarationOf } from '../being/being.js';
|
|
4
|
+
export class ClassList {
|
|
5
|
+
#kinds = new Map();
|
|
6
|
+
#steward;
|
|
7
|
+
#public;
|
|
8
|
+
constructor({ steward, public: open, beings = [] }) {
|
|
9
|
+
const kindOf = (Class) => {
|
|
10
|
+
const kind = declarationOf(Class)?.kind;
|
|
11
|
+
if (typeof kind !== 'string')
|
|
12
|
+
throw new TypeError('a class in the list is not a class of Being.of');
|
|
13
|
+
const held = this.#kinds.get(kind);
|
|
14
|
+
if (held !== undefined && held !== Class)
|
|
15
|
+
throw new TypeError(`two classes claim the kind ${kind}`);
|
|
16
|
+
this.#kinds.set(kind, Class);
|
|
17
|
+
return kind;
|
|
18
|
+
};
|
|
19
|
+
this.#steward = kindOf(steward);
|
|
20
|
+
this.#public = open === undefined ? undefined : kindOf(open);
|
|
21
|
+
for (const Class of beings)
|
|
22
|
+
kindOf(Class);
|
|
23
|
+
}
|
|
24
|
+
async resolve({ kind }) {
|
|
25
|
+
return this.#kinds.get(kind);
|
|
26
|
+
}
|
|
27
|
+
steward() {
|
|
28
|
+
return this.#steward;
|
|
29
|
+
}
|
|
30
|
+
public() {
|
|
31
|
+
return this.#public;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Carry, Sent } from '../foundation.ts';
|
|
2
|
+
export declare class JoinedCarry implements Carry {
|
|
3
|
+
#private;
|
|
4
|
+
/** `carries` by scheme: `{ tcp: tcpCarry, https: webCarry }`. */
|
|
5
|
+
constructor(carries: Readonly<Record<string, Carry>>);
|
|
6
|
+
/** A ward's door, hooked to every carry here that listens. */
|
|
7
|
+
listen(options: {
|
|
8
|
+
ward: string;
|
|
9
|
+
door: (box: Uint8Array) => Promise<Uint8Array | null>;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
/** A ward unhooked from every carry here that listens. */
|
|
12
|
+
unlisten(options: {
|
|
13
|
+
ward: string;
|
|
14
|
+
}): void;
|
|
15
|
+
send({ ward, at, box }: {
|
|
16
|
+
ward: string;
|
|
17
|
+
at: readonly string[];
|
|
18
|
+
box: Uint8Array;
|
|
19
|
+
}): Promise<Sent>;
|
|
20
|
+
at(options?: {
|
|
21
|
+
toward?: string;
|
|
22
|
+
}): readonly string[];
|
|
23
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class JoinedCarry {
|
|
2
|
+
#carries;
|
|
3
|
+
// Each carry once, where one serves two schemes, as the web serves http and https.
|
|
4
|
+
#each;
|
|
5
|
+
/** `carries` by scheme: `{ tcp: tcpCarry, https: webCarry }`. */
|
|
6
|
+
constructor(carries) {
|
|
7
|
+
this.#carries = new Map(Object.entries(carries));
|
|
8
|
+
this.#each = [...new Set(this.#carries.values())];
|
|
9
|
+
}
|
|
10
|
+
/** A ward's door, hooked to every carry here that listens. */
|
|
11
|
+
async listen(options) {
|
|
12
|
+
for (const carry of this.#each)
|
|
13
|
+
await carry.listen?.(options);
|
|
14
|
+
}
|
|
15
|
+
/** A ward unhooked from every carry here that listens. */
|
|
16
|
+
unlisten(options) {
|
|
17
|
+
for (const carry of this.#each)
|
|
18
|
+
carry.unlisten?.(options);
|
|
19
|
+
}
|
|
20
|
+
async send({ ward, at, box }) {
|
|
21
|
+
// A carry that listens for the ward hands it the box by pointer, and no address is dialled.
|
|
22
|
+
for (const carry of this.#each) {
|
|
23
|
+
const sent = await carry.send({ ward, at: [], box });
|
|
24
|
+
if (sent.reply !== null)
|
|
25
|
+
return sent;
|
|
26
|
+
}
|
|
27
|
+
for (const address of at) {
|
|
28
|
+
// A scheme is read in any case, as a URI's is.
|
|
29
|
+
const scheme = address.slice(0, Math.max(0, address.indexOf('://'))).toLowerCase();
|
|
30
|
+
const carry = this.#carries.get(scheme);
|
|
31
|
+
if (carry === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
const sent = await carry.send({ ward, at: [address], box });
|
|
34
|
+
if (sent.reply !== null || sent.heard)
|
|
35
|
+
return sent;
|
|
36
|
+
}
|
|
37
|
+
return { reply: null, heard: false };
|
|
38
|
+
}
|
|
39
|
+
at(options = {}) {
|
|
40
|
+
return this.#each.flatMap((carry) => carry.at(options));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Crypto } from '../foundation.ts';
|
|
2
|
+
export declare class NobleCrypto implements Crypto {
|
|
3
|
+
random(length: number): Uint8Array;
|
|
4
|
+
sha256(data: Uint8Array): Promise<Uint8Array>;
|
|
5
|
+
hkdf(ikm: Uint8Array, info: string, length: number): Promise<Uint8Array>;
|
|
6
|
+
seal(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, additional: Uint8Array): Promise<Uint8Array>;
|
|
7
|
+
open(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, additional: Uint8Array): Promise<Uint8Array | null>;
|
|
8
|
+
signingPublic(secret: Uint8Array): Promise<Uint8Array>;
|
|
9
|
+
sign(secret: Uint8Array, message: Uint8Array): Promise<Uint8Array>;
|
|
10
|
+
verify(pk: Uint8Array, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
11
|
+
agreePublic(secret: Uint8Array): Promise<Uint8Array>;
|
|
12
|
+
agree(secret: Uint8Array, pk: Uint8Array): Promise<Uint8Array | null>;
|
|
13
|
+
lockPair(seed: Uint8Array): Promise<{
|
|
14
|
+
encapsulation: Uint8Array;
|
|
15
|
+
decapsulation: Uint8Array;
|
|
16
|
+
}>;
|
|
17
|
+
encapsulate(encapsulation: Uint8Array): Promise<{
|
|
18
|
+
ciphertext: Uint8Array;
|
|
19
|
+
shared: Uint8Array;
|
|
20
|
+
} | null>;
|
|
21
|
+
decapsulate(decapsulation: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The default crypto: Ed25519, X25519 and ML-KEM-768 from noble, SHA-256,
|
|
3
|
+
// HKDF and AES-GCM from Web Crypto.
|
|
4
|
+
import { ed25519, x25519 } from '@noble/curves/ed25519.js';
|
|
5
|
+
import { ml_kem768 } from '@noble/post-quantum/ml-kem.js';
|
|
6
|
+
const subtle = globalThis.crypto.subtle;
|
|
7
|
+
const ascii = new TextEncoder();
|
|
8
|
+
const view = (bytes) => new Uint8Array(bytes);
|
|
9
|
+
export class NobleCrypto {
|
|
10
|
+
random(length) {
|
|
11
|
+
const out = new Uint8Array(length);
|
|
12
|
+
for (let at = 0; at < length; at += 65_536)
|
|
13
|
+
globalThis.crypto.getRandomValues(out.subarray(at, Math.min(length, at + 65_536)));
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
async sha256(data) {
|
|
17
|
+
return new Uint8Array(await subtle.digest('SHA-256', view(data)));
|
|
18
|
+
}
|
|
19
|
+
async hkdf(ikm, info, length) {
|
|
20
|
+
const key = await subtle.importKey('raw', view(ikm), 'HKDF', false, ['deriveBits']);
|
|
21
|
+
const bits = await subtle.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(0), info: ascii.encode(info) }, key, length * 8);
|
|
22
|
+
return new Uint8Array(bits);
|
|
23
|
+
}
|
|
24
|
+
async seal(key, nonce, plaintext, additional) {
|
|
25
|
+
const aes = await subtle.importKey('raw', view(key), 'AES-GCM', false, ['encrypt']);
|
|
26
|
+
return new Uint8Array(await subtle.encrypt({ name: 'AES-GCM', iv: view(nonce), additionalData: view(additional) }, aes, view(plaintext)));
|
|
27
|
+
}
|
|
28
|
+
async open(key, nonce, ciphertext, additional) {
|
|
29
|
+
if (ciphertext.length < 16)
|
|
30
|
+
return null;
|
|
31
|
+
const aes = await subtle.importKey('raw', view(key), 'AES-GCM', false, ['decrypt']);
|
|
32
|
+
try {
|
|
33
|
+
return new Uint8Array(await subtle.decrypt({ name: 'AES-GCM', iv: view(nonce), additionalData: view(additional) }, aes, view(ciphertext)));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async signingPublic(secret) {
|
|
40
|
+
return ed25519.getPublicKey(secret);
|
|
41
|
+
}
|
|
42
|
+
async sign(secret, message) {
|
|
43
|
+
return ed25519.sign(message, secret);
|
|
44
|
+
}
|
|
45
|
+
async verify(pk, message, signature) {
|
|
46
|
+
if (pk.length !== 32 || signature.length !== 64)
|
|
47
|
+
return false;
|
|
48
|
+
try {
|
|
49
|
+
// Strict decoding refuses a y at or above the prime and a non-canonical R.
|
|
50
|
+
if (ed25519.Point.fromBytes(pk, false).isSmallOrder())
|
|
51
|
+
return false;
|
|
52
|
+
return ed25519.verify(signature, message, pk, { zip215: false });
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async agreePublic(secret) {
|
|
59
|
+
return x25519.getPublicKey(secret);
|
|
60
|
+
}
|
|
61
|
+
async agree(secret, pk) {
|
|
62
|
+
if (pk.length !== 32)
|
|
63
|
+
return null;
|
|
64
|
+
try {
|
|
65
|
+
const shared = x25519.getSharedSecret(secret, pk);
|
|
66
|
+
return shared.every((byte) => byte === 0) ? null : shared;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async lockPair(seed) {
|
|
73
|
+
const { publicKey, secretKey } = ml_kem768.keygen(seed);
|
|
74
|
+
return { encapsulation: publicKey, decapsulation: secretKey };
|
|
75
|
+
}
|
|
76
|
+
async encapsulate(encapsulation) {
|
|
77
|
+
try {
|
|
78
|
+
const { cipherText, sharedSecret } = ml_kem768.encapsulate(encapsulation);
|
|
79
|
+
return { ciphertext: cipherText, shared: sharedSecret };
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async decapsulate(decapsulation, ciphertext) {
|
|
86
|
+
return ml_kem768.decapsulate(ciphertext, decapsulation);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Crypto, Keys, WardKeys } from '../foundation.ts';
|
|
2
|
+
export declare class SeedKeys implements Keys {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(seed: string | undefined, crypto?: Crypto);
|
|
5
|
+
ward(): Promise<WardKeys>;
|
|
6
|
+
sign(message: Uint8Array): Promise<Uint8Array>;
|
|
7
|
+
agree(pk: Uint8Array): Promise<Uint8Array | null>;
|
|
8
|
+
decapsulate(ciphertext: Uint8Array): Promise<Uint8Array>;
|
|
9
|
+
derive(label: string, length: number): Promise<Uint8Array>;
|
|
10
|
+
}
|