@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.
Files changed (92) hide show
  1. package/README.md +2601 -2
  2. package/RELEASE.json +123 -0
  3. package/export-page/coi-sw.js +20 -0
  4. package/export-page/index.html +17 -0
  5. package/export-page/run.css +10 -0
  6. package/export-page/run.js +236 -0
  7. package/guest/arm64/Image +4 -0
  8. package/guest/arm64/Image-4k +4 -0
  9. package/guest/arm64/initramfs.cpio.gz +0 -0
  10. package/guest/arm64/vm.wasm +0 -0
  11. package/package.json +36 -4
  12. package/page/sw.js +381 -0
  13. package/relay/control.ts +139 -0
  14. package/relay/engine.ts +787 -0
  15. package/relay/hosted.ts +660 -0
  16. package/relay/keys.ts +53 -0
  17. package/relay/local-tls.ts +121 -0
  18. package/relay/lock.ts +81 -0
  19. package/relay/logfile.ts +138 -0
  20. package/relay/main.ts +642 -0
  21. package/relay/policy.ts +241 -0
  22. package/relay/relay.ts +266 -0
  23. package/relay/token.ts +116 -0
  24. package/relay/v1.ts +107 -0
  25. package/relay/wisp.ts +615 -0
  26. package/src/base.js +325 -0
  27. package/src/boot.js +2037 -0
  28. package/src/calib-worker.js +40 -0
  29. package/src/fs/9p/codec.js +303 -0
  30. package/src/fs/9p/server.js +521 -0
  31. package/src/fs/errno.js +43 -0
  32. package/src/fs/graph.js +380 -0
  33. package/src/fs/overlay.js +1018 -0
  34. package/src/fs/pack-write.js +165 -0
  35. package/src/fs/pack.js +427 -0
  36. package/src/fs/seed.js +22 -0
  37. package/src/fs/svc-snapshot.js +65 -0
  38. package/src/fs/upper-pack.js +112 -0
  39. package/src/host/base-cache.js +195 -0
  40. package/src/host/base-file.js +328 -0
  41. package/src/host/base-io.js +93 -0
  42. package/src/host/boot-events.js +209 -0
  43. package/src/host/chan-service.js +300 -0
  44. package/src/host/chan-services.js +689 -0
  45. package/src/host/chan.js +655 -0
  46. package/src/host/ckpt-file.js +395 -0
  47. package/src/host/ckpt-io.js +207 -0
  48. package/src/host/ckpt-point.js +160 -0
  49. package/src/host/ckpt-save.js +64 -0
  50. package/src/host/ckpt.js +434 -0
  51. package/src/host/clock.js +111 -0
  52. package/src/host/ctl.js +778 -0
  53. package/src/host/errno.js +18 -0
  54. package/src/host/fs.js +167 -0
  55. package/src/host/gcnudge.js +83 -0
  56. package/src/host/i64shim.js +81 -0
  57. package/src/host/jit-cache.js +341 -0
  58. package/src/host/jit-hub-core.js +208 -0
  59. package/src/host/jit-hub.js +93 -0
  60. package/src/host/jit-snap.js +226 -0
  61. package/src/host/jit.js +1903 -0
  62. package/src/host/layout.js +100 -0
  63. package/src/host/monitor.js +110 -0
  64. package/src/host/p9mount.js +27 -0
  65. package/src/host/ram-mem.js +125 -0
  66. package/src/host/stdio.js +143 -0
  67. package/src/host/threads.js +109 -0
  68. package/src/host/wasi.js +335 -0
  69. package/src/host/wmd-imports.js +71 -0
  70. package/src/host/yamux.js +387 -0
  71. package/src/image-files.js +154 -0
  72. package/src/net/dhcp-dns.js +126 -0
  73. package/src/net/net-worker.js +172 -0
  74. package/src/net/packet.js +227 -0
  75. package/src/net/peer.js +230 -0
  76. package/src/net/proto.js +189 -0
  77. package/src/net/relay-client.js +226 -0
  78. package/src/net/stack.js +867 -0
  79. package/src/net/switch.js +160 -0
  80. package/src/net/wisp-client.js +570 -0
  81. package/src/net/wisp-transport.js +196 -0
  82. package/src/net/wisp.js +511 -0
  83. package/src/registry/client.js +176 -0
  84. package/src/registry/converter.js +68 -0
  85. package/src/registry/hub.js +221 -0
  86. package/src/registry/oci.js +76 -0
  87. package/src/registry/pack-worker.js +70 -0
  88. package/src/registry/pull.js +258 -0
  89. package/src/registry/ref.js +110 -0
  90. package/src/registry/wasi-mem.js +355 -0
  91. package/src/worker.js +315 -0
  92. 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
+ }