@threadedstack/wmd-web 0.0.0-stage → 0.1.12
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 +2601 -2
- package/RELEASE.json +123 -0
- package/export-page/coi-sw.js +20 -0
- package/export-page/index.html +17 -0
- package/export-page/run.css +10 -0
- package/export-page/run.js +236 -0
- package/guest/arm64/Image +4 -0
- package/guest/arm64/Image-4k +4 -0
- package/guest/arm64/initramfs.cpio.gz +0 -0
- package/guest/arm64/vm.wasm +0 -0
- package/package.json +36 -4
- package/page/sw.js +381 -0
- package/relay/control.ts +139 -0
- package/relay/engine.ts +787 -0
- package/relay/hosted.ts +660 -0
- package/relay/keys.ts +53 -0
- package/relay/local-tls.ts +121 -0
- package/relay/lock.ts +81 -0
- package/relay/logfile.ts +138 -0
- package/relay/main.ts +642 -0
- package/relay/policy.ts +241 -0
- package/relay/relay.ts +266 -0
- package/relay/token.ts +116 -0
- package/relay/v1.ts +107 -0
- package/relay/wisp.ts +615 -0
- package/src/base.js +325 -0
- package/src/boot.js +2037 -0
- package/src/calib-worker.js +40 -0
- package/src/fs/9p/codec.js +303 -0
- package/src/fs/9p/server.js +521 -0
- package/src/fs/errno.js +43 -0
- package/src/fs/graph.js +380 -0
- package/src/fs/overlay.js +1018 -0
- package/src/fs/pack-write.js +165 -0
- package/src/fs/pack.js +427 -0
- package/src/fs/seed.js +22 -0
- package/src/fs/svc-snapshot.js +65 -0
- package/src/fs/upper-pack.js +112 -0
- package/src/host/base-cache.js +195 -0
- package/src/host/base-file.js +328 -0
- package/src/host/base-io.js +93 -0
- package/src/host/boot-events.js +209 -0
- package/src/host/chan-service.js +300 -0
- package/src/host/chan-services.js +689 -0
- package/src/host/chan.js +655 -0
- package/src/host/ckpt-file.js +395 -0
- package/src/host/ckpt-io.js +207 -0
- package/src/host/ckpt-point.js +160 -0
- package/src/host/ckpt-save.js +64 -0
- package/src/host/ckpt.js +434 -0
- package/src/host/clock.js +111 -0
- package/src/host/ctl.js +778 -0
- package/src/host/errno.js +18 -0
- package/src/host/fs.js +167 -0
- package/src/host/gcnudge.js +83 -0
- package/src/host/i64shim.js +81 -0
- package/src/host/jit-cache.js +341 -0
- package/src/host/jit-hub-core.js +208 -0
- package/src/host/jit-hub.js +93 -0
- package/src/host/jit-snap.js +226 -0
- package/src/host/jit.js +1903 -0
- package/src/host/layout.js +100 -0
- package/src/host/monitor.js +110 -0
- package/src/host/p9mount.js +27 -0
- package/src/host/ram-mem.js +125 -0
- package/src/host/stdio.js +143 -0
- package/src/host/threads.js +109 -0
- package/src/host/wasi.js +335 -0
- package/src/host/wmd-imports.js +71 -0
- package/src/host/yamux.js +387 -0
- package/src/image-files.js +154 -0
- package/src/net/dhcp-dns.js +126 -0
- package/src/net/net-worker.js +172 -0
- package/src/net/packet.js +227 -0
- package/src/net/peer.js +230 -0
- package/src/net/proto.js +189 -0
- package/src/net/relay-client.js +226 -0
- package/src/net/stack.js +867 -0
- package/src/net/switch.js +160 -0
- package/src/net/wisp-client.js +570 -0
- package/src/net/wisp-transport.js +196 -0
- package/src/net/wisp.js +511 -0
- package/src/registry/client.js +176 -0
- package/src/registry/converter.js +68 -0
- package/src/registry/hub.js +221 -0
- package/src/registry/oci.js +76 -0
- package/src/registry/pack-worker.js +70 -0
- package/src/registry/pull.js +258 -0
- package/src/registry/ref.js +110 -0
- package/src/registry/wasi-mem.js +355 -0
- package/src/worker.js +315 -0
- package/wmdpack.wasm +0 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// W56: an in-page network switch. The guests of one page that share a NetSwitch reach each other by
|
|
2
|
+
// address and by name, with no relay. Each guest's network Worker (net-worker.js) keeps its own
|
|
3
|
+
// stack (stack.js); the switch is the control plane only: it hands out addresses, keeps the name
|
|
4
|
+
// table, and links two Workers with a MessageChannel the first time one of them opens a connection
|
|
5
|
+
// to the other. Streams then go Worker to Worker (peer.js), never through the page.
|
|
6
|
+
//
|
|
7
|
+
// const sw = new NetSwitch()
|
|
8
|
+
// bootWmd({ ..., channels: { services: [..., { port: 9, kind: 'net', switch: sw, name: 'db' }] } })
|
|
9
|
+
//
|
|
10
|
+
// A guest with no switch is alone on its network, as before. Switches are separate networks: two
|
|
11
|
+
// documents with one switch each never see each other's guests.
|
|
12
|
+
//
|
|
13
|
+
// Worker <-> switch (the member's MessagePort):
|
|
14
|
+
// worker -> switch {t:'link', ip} a port to the member at ip, please
|
|
15
|
+
// switch -> worker {t:'table', members} [{name, ip}] every member (sent on attach and on each change)
|
|
16
|
+
// {t:'link', ip, port} a MessagePort to the member at ip (both ends get one)
|
|
17
|
+
// {t:'nolink', ip} no member has ip
|
|
18
|
+
// {t:'gone', ip} the member at ip left: its streams are over
|
|
19
|
+
//
|
|
20
|
+
// Addresses: the engine's network (192.168.127.0/24, gateway .1). A member gets the address asked
|
|
21
|
+
// for, else the one its name had before on this switch (a guest restored from a checkpoint keeps
|
|
22
|
+
// the address in its memory), else the lowest free one from .2.
|
|
23
|
+
|
|
24
|
+
import { ipToU32, u32ToIp } from './packet.js'
|
|
25
|
+
|
|
26
|
+
const NAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/
|
|
27
|
+
|
|
28
|
+
export class NetSwitch {
|
|
29
|
+
/**
|
|
30
|
+
* @param {object} [o]
|
|
31
|
+
* @param {string} [o.gateway] the gateway's address (default 192.168.127.1)
|
|
32
|
+
* @param {string} [o.mask] the network's mask (default 255.255.255.0)
|
|
33
|
+
*/
|
|
34
|
+
constructor(o = {}) {
|
|
35
|
+
this.gw = ipToU32(o.gateway || '192.168.127.1')
|
|
36
|
+
this.mask = ipToU32(o.mask || '255.255.255.0')
|
|
37
|
+
this.net = (this.gw & this.mask) >>> 0
|
|
38
|
+
this.bcast = (this.net | ~this.mask) >>> 0
|
|
39
|
+
this.members = new Map() // name -> member
|
|
40
|
+
this.byIp = new Map() // u32 -> member
|
|
41
|
+
this.reserved = new Map() // name -> u32: the address a name had (or was given by address())
|
|
42
|
+
this.links = new Set() // 'ipA-ipB' (lower first) of linked pairs
|
|
43
|
+
this.closed = false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The address `name` gets on this switch (reserved for it from now on). */
|
|
47
|
+
address(name, ip) {
|
|
48
|
+
return u32ToIp(this.#addressFor(checkName(name), ip))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#addressFor(name, ip) {
|
|
52
|
+
if (ip !== undefined && ip !== null && ip !== '') {
|
|
53
|
+
const m = typeof ip === 'string' && /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(ip)
|
|
54
|
+
if (!m || m.slice(1).some(x => Number(x) > 255)) throw new Error(`net switch: ip ${JSON.stringify(ip)} is not a dotted IPv4 address`)
|
|
55
|
+
const a = ipToU32(ip)
|
|
56
|
+
if (((a & this.mask) >>> 0) !== this.net || a === this.net || a === this.bcast || a === this.gw) {
|
|
57
|
+
throw new Error(`net switch: ${ip} is not a guest address on ${u32ToIp(this.net)}/${maskBits(this.mask)} (gateway ${u32ToIp(this.gw)})`)
|
|
58
|
+
}
|
|
59
|
+
for (const [n, r] of this.reserved) if (r === a && n !== name) throw new Error(`net switch: ${ip} is taken by ${n}`)
|
|
60
|
+
this.reserved.set(name, a)
|
|
61
|
+
return a
|
|
62
|
+
}
|
|
63
|
+
const had = this.reserved.get(name)
|
|
64
|
+
if (had !== undefined) return had
|
|
65
|
+
const taken = new Set(this.reserved.values())
|
|
66
|
+
for (let a = (this.net + 1) >>> 0; a < this.bcast; a++) {
|
|
67
|
+
if (a === this.gw || taken.has(a)) continue
|
|
68
|
+
this.reserved.set(name, a)
|
|
69
|
+
return a
|
|
70
|
+
}
|
|
71
|
+
throw new Error('net switch: no free address')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Attach a guest (bootWmd does this for a net service with `switch`). Returns its address and the
|
|
76
|
+
* MessagePort for its network Worker; detach() takes it off (its peers' streams to it fail).
|
|
77
|
+
*/
|
|
78
|
+
attach({ name, ip } = {}) {
|
|
79
|
+
if (this.closed) throw new Error('net switch: closed')
|
|
80
|
+
name = checkName(name)
|
|
81
|
+
if (this.members.has(name)) throw new Error(`net switch: a guest named ${name} is already on the switch`)
|
|
82
|
+
const a = this.#addressFor(name, ip)
|
|
83
|
+
if (this.byIp.has(a)) throw new Error(`net switch: ${u32ToIp(a)} is in use by ${this.byIp.get(a).name}`)
|
|
84
|
+
const mc = new MessageChannel()
|
|
85
|
+
const m = { name, ip: a, port: mc.port1, peers: new Set(), detached: false }
|
|
86
|
+
m.port.onmessage = e => this.#onMessage(m, e.data)
|
|
87
|
+
this.members.set(name, m)
|
|
88
|
+
this.byIp.set(a, m)
|
|
89
|
+
this.#broadcastTable()
|
|
90
|
+
return {
|
|
91
|
+
name,
|
|
92
|
+
ip: u32ToIp(a),
|
|
93
|
+
port: mc.port2,
|
|
94
|
+
detach: () => this.#detach(m),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#onMessage(m, x) {
|
|
99
|
+
if (m.detached || !x || x.t !== 'link') return
|
|
100
|
+
const peer = this.byIp.get(x.ip >>> 0)
|
|
101
|
+
if (!peer || peer === m) {
|
|
102
|
+
m.port.postMessage({ t: 'nolink', ip: x.ip })
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
const key = m.ip < peer.ip ? `${m.ip}-${peer.ip}` : `${peer.ip}-${m.ip}`
|
|
106
|
+
if (this.links.has(key)) return // the two are linked already: the port is on its way (both asked at once)
|
|
107
|
+
this.links.add(key)
|
|
108
|
+
const mc = new MessageChannel()
|
|
109
|
+
m.peers.add(peer)
|
|
110
|
+
peer.peers.add(m)
|
|
111
|
+
m.port.postMessage({ t: 'link', ip: peer.ip, port: mc.port1 }, [mc.port1])
|
|
112
|
+
peer.port.postMessage({ t: 'link', ip: m.ip, port: mc.port2 }, [mc.port2])
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#detach(m) {
|
|
116
|
+
if (m.detached) return
|
|
117
|
+
m.detached = true
|
|
118
|
+
this.members.delete(m.name)
|
|
119
|
+
this.byIp.delete(m.ip)
|
|
120
|
+
for (const p of m.peers) {
|
|
121
|
+
p.peers.delete(m)
|
|
122
|
+
const key = m.ip < p.ip ? `${m.ip}-${p.ip}` : `${p.ip}-${m.ip}`
|
|
123
|
+
this.links.delete(key)
|
|
124
|
+
p.port.postMessage({ t: 'gone', ip: m.ip })
|
|
125
|
+
}
|
|
126
|
+
m.peers.clear()
|
|
127
|
+
m.port.onmessage = null
|
|
128
|
+
m.port.close()
|
|
129
|
+
this.#broadcastTable()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#broadcastTable() {
|
|
133
|
+
const members = this.table()
|
|
134
|
+
const t = members.map(x => ({ name: x.name, ip: ipToU32(x.ip) }))
|
|
135
|
+
for (const m of this.members.values()) m.port.postMessage({ t: 'table', members: t })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The guests on the switch: [{name, ip}]. */
|
|
139
|
+
table() {
|
|
140
|
+
return [...this.members.values()].map(m => ({ name: m.name, ip: u32ToIp(m.ip) }))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Take every guest off (their streams to each other fail); the switch takes no more. */
|
|
144
|
+
close() {
|
|
145
|
+
this.closed = true
|
|
146
|
+
for (const m of [...this.members.values()]) this.#detach(m)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function checkName(name) {
|
|
151
|
+
const n = String(name ?? '').toLowerCase()
|
|
152
|
+
if (!NAME_RE.test(n)) throw new Error(`net switch: a guest's name must be a DNS label (a-z, 0-9, '-', up to 63), not ${JSON.stringify(name)}`)
|
|
153
|
+
return n
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function maskBits(m) {
|
|
157
|
+
let n = 0
|
|
158
|
+
for (let v = m >>> 0; v; v = (v << 1) >>> 0) n++
|
|
159
|
+
return n
|
|
160
|
+
}
|