@aptre/v86 0.5.0

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 (111) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSE.MIT +22 -0
  3. package/Readme.md +237 -0
  4. package/dist/v86.browser.js +26666 -0
  5. package/dist/v86.browser.js.map +7 -0
  6. package/dist/v86.js +26632 -0
  7. package/dist/v86.js.map +7 -0
  8. package/gen/generate_analyzer.ts +512 -0
  9. package/gen/generate_interpreter.ts +522 -0
  10. package/gen/generate_jit.ts +624 -0
  11. package/gen/rust_ast.ts +107 -0
  12. package/gen/util.ts +35 -0
  13. package/gen/x86_table.ts +1836 -0
  14. package/lib/9p.ts +1547 -0
  15. package/lib/filesystem.ts +1879 -0
  16. package/lib/marshall.ts +168 -0
  17. package/lib/softfloat/softfloat.c +32501 -0
  18. package/lib/zstd/zstddeclib.c +13520 -0
  19. package/package.json +75 -0
  20. package/src/acpi.ts +267 -0
  21. package/src/browser/dummy_screen.ts +106 -0
  22. package/src/browser/fake_network.ts +1771 -0
  23. package/src/browser/fetch_network.ts +361 -0
  24. package/src/browser/filestorage.ts +124 -0
  25. package/src/browser/inbrowser_network.ts +57 -0
  26. package/src/browser/keyboard.ts +564 -0
  27. package/src/browser/main.ts +3415 -0
  28. package/src/browser/mouse.ts +255 -0
  29. package/src/browser/network.ts +142 -0
  30. package/src/browser/print_stats.ts +336 -0
  31. package/src/browser/screen.ts +978 -0
  32. package/src/browser/serial.ts +316 -0
  33. package/src/browser/speaker.ts +1223 -0
  34. package/src/browser/starter.ts +1688 -0
  35. package/src/browser/wisp_network.ts +332 -0
  36. package/src/browser/worker_bus.ts +64 -0
  37. package/src/buffer.ts +652 -0
  38. package/src/bus.ts +78 -0
  39. package/src/const.ts +128 -0
  40. package/src/cpu.ts +2891 -0
  41. package/src/dma.ts +474 -0
  42. package/src/elf.ts +251 -0
  43. package/src/floppy.ts +1778 -0
  44. package/src/ide.ts +3455 -0
  45. package/src/io.ts +504 -0
  46. package/src/iso9660.ts +317 -0
  47. package/src/kernel.ts +250 -0
  48. package/src/lib.ts +645 -0
  49. package/src/log.ts +149 -0
  50. package/src/main.ts +199 -0
  51. package/src/ne2k.ts +1589 -0
  52. package/src/pci.ts +815 -0
  53. package/src/pit.ts +406 -0
  54. package/src/ps2.ts +820 -0
  55. package/src/rtc.ts +537 -0
  56. package/src/rust/analysis.rs +101 -0
  57. package/src/rust/codegen.rs +2660 -0
  58. package/src/rust/config.rs +3 -0
  59. package/src/rust/control_flow.rs +425 -0
  60. package/src/rust/cpu/apic.rs +658 -0
  61. package/src/rust/cpu/arith.rs +1207 -0
  62. package/src/rust/cpu/call_indirect.rs +2 -0
  63. package/src/rust/cpu/cpu.rs +4501 -0
  64. package/src/rust/cpu/fpu.rs +923 -0
  65. package/src/rust/cpu/global_pointers.rs +112 -0
  66. package/src/rust/cpu/instructions.rs +2486 -0
  67. package/src/rust/cpu/instructions_0f.rs +5261 -0
  68. package/src/rust/cpu/ioapic.rs +316 -0
  69. package/src/rust/cpu/memory.rs +351 -0
  70. package/src/rust/cpu/misc_instr.rs +613 -0
  71. package/src/rust/cpu/mod.rs +16 -0
  72. package/src/rust/cpu/modrm.rs +133 -0
  73. package/src/rust/cpu/pic.rs +402 -0
  74. package/src/rust/cpu/sse_instr.rs +361 -0
  75. package/src/rust/cpu/string.rs +701 -0
  76. package/src/rust/cpu/vga.rs +175 -0
  77. package/src/rust/cpu_context.rs +69 -0
  78. package/src/rust/dbg.rs +98 -0
  79. package/src/rust/gen/analyzer.rs +3807 -0
  80. package/src/rust/gen/analyzer0f.rs +3992 -0
  81. package/src/rust/gen/interpreter.rs +4447 -0
  82. package/src/rust/gen/interpreter0f.rs +5404 -0
  83. package/src/rust/gen/jit.rs +5080 -0
  84. package/src/rust/gen/jit0f.rs +5547 -0
  85. package/src/rust/gen/mod.rs +14 -0
  86. package/src/rust/jit.rs +2443 -0
  87. package/src/rust/jit_instructions.rs +7881 -0
  88. package/src/rust/js_api.rs +6 -0
  89. package/src/rust/leb.rs +46 -0
  90. package/src/rust/lib.rs +29 -0
  91. package/src/rust/modrm.rs +330 -0
  92. package/src/rust/opstats.rs +249 -0
  93. package/src/rust/page.rs +15 -0
  94. package/src/rust/paging.rs +25 -0
  95. package/src/rust/prefix.rs +15 -0
  96. package/src/rust/profiler.rs +155 -0
  97. package/src/rust/regs.rs +38 -0
  98. package/src/rust/softfloat.rs +286 -0
  99. package/src/rust/state_flags.rs +27 -0
  100. package/src/rust/wasmgen/mod.rs +2 -0
  101. package/src/rust/wasmgen/wasm_builder.rs +1047 -0
  102. package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
  103. package/src/rust/zstd.rs +105 -0
  104. package/src/sb16.ts +1928 -0
  105. package/src/state.ts +359 -0
  106. package/src/uart.ts +472 -0
  107. package/src/vga.ts +2791 -0
  108. package/src/virtio.ts +1756 -0
  109. package/src/virtio_balloon.ts +273 -0
  110. package/src/virtio_console.ts +372 -0
  111. package/src/virtio_net.ts +326 -0
@@ -0,0 +1,361 @@
1
+ import { LOG_FETCH } from '../const.js'
2
+ import { dbg_log } from '../log.js'
3
+
4
+ import {
5
+ create_eth_encoder_buf,
6
+ handle_fake_networking,
7
+ TCPConnection,
8
+ fake_tcp_connect,
9
+ fake_tcp_probe,
10
+ } from './fake_network.js'
11
+
12
+ import type { EthEncoderBuf, NetworkAdapterLike } from './fake_network.js'
13
+
14
+ import { BusConnector } from '../bus.js'
15
+
16
+ interface FetchNetworkConfig {
17
+ id?: number
18
+ router_mac?: string
19
+ router_ip?: string
20
+ vm_ip?: string
21
+ masquerade?: boolean
22
+ dns_method?: string
23
+ doh_server?: string
24
+ mtu?: number
25
+ cors_proxy?: string
26
+ }
27
+
28
+ export class FetchNetworkAdapter implements NetworkAdapterLike {
29
+ bus: BusConnector
30
+ id: number
31
+ router_mac: Uint8Array
32
+ router_ip: Uint8Array
33
+ vm_ip: Uint8Array
34
+ masquerade: boolean
35
+ vm_mac: Uint8Array
36
+ dns_method: string
37
+ doh_server: string | undefined
38
+ tcp_conn: Record<string, TCPConnection>
39
+ mtu: number | undefined
40
+ eth_encoder_buf: EthEncoderBuf
41
+ cors_proxy: string | undefined
42
+
43
+ constructor(bus: BusConnector, config?: FetchNetworkConfig) {
44
+ config = config || {}
45
+ this.bus = bus
46
+ this.id = config.id || 0
47
+ this.router_mac = new Uint8Array(
48
+ (config.router_mac || '52:54:0:1:2:3').split(':').map(function (x) {
49
+ return parseInt(x, 16)
50
+ }),
51
+ )
52
+ this.router_ip = new Uint8Array(
53
+ (config.router_ip || '192.168.86.1').split('.').map(function (x) {
54
+ return parseInt(x, 10)
55
+ }),
56
+ )
57
+ this.vm_ip = new Uint8Array(
58
+ (config.vm_ip || '192.168.86.100').split('.').map(function (x) {
59
+ return parseInt(x, 10)
60
+ }),
61
+ )
62
+ this.masquerade = config.masquerade === undefined || !!config.masquerade
63
+ this.vm_mac = new Uint8Array(6)
64
+ this.dns_method = config.dns_method || 'static'
65
+ this.doh_server = config.doh_server
66
+ this.tcp_conn = {}
67
+ this.mtu = config.mtu
68
+ this.eth_encoder_buf = create_eth_encoder_buf(this.mtu)
69
+
70
+ // Ex: 'https://corsproxy.io/?'
71
+ this.cors_proxy = config.cors_proxy
72
+
73
+ this.bus.register(
74
+ 'net' + this.id + '-mac',
75
+ function (this: FetchNetworkAdapter, mac: string) {
76
+ this.vm_mac = new Uint8Array(
77
+ mac.split(':').map(function (x) {
78
+ return parseInt(x, 16)
79
+ }),
80
+ )
81
+ },
82
+ this,
83
+ )
84
+ this.bus.register(
85
+ 'net' + this.id + '-send',
86
+ function (this: FetchNetworkAdapter, data: Uint8Array) {
87
+ this.send(data)
88
+ },
89
+ this,
90
+ )
91
+ this.bus.register(
92
+ 'tcp-connection',
93
+ (conn: TCPConnection) => {
94
+ if (conn.sport === 80) {
95
+ conn.on('data', on_data_http.bind(conn))
96
+ conn.accept()
97
+ }
98
+ },
99
+ this,
100
+ )
101
+ }
102
+
103
+ destroy(): void {}
104
+
105
+ connect(port: number): TCPConnection {
106
+ return fake_tcp_connect(port, this)
107
+ }
108
+
109
+ tcp_probe(port: number): Promise<boolean> {
110
+ return fake_tcp_probe(port, this)
111
+ }
112
+
113
+ async fetch_resource(
114
+ url: string,
115
+ options?: RequestInit,
116
+ ): Promise<
117
+ [
118
+ Response | { status: number; statusText: string; headers: Headers },
119
+ ArrayBuffer,
120
+ ]
121
+ > {
122
+ if (this.cors_proxy) url = this.cors_proxy + encodeURIComponent(url)
123
+
124
+ try {
125
+ const resp = await fetch(url, options)
126
+ const ab = await resp.arrayBuffer()
127
+ return [resp, ab]
128
+ } catch (e) {
129
+ const err = e instanceof Error ? e : new Error(String(e))
130
+ console.warn('Fetch Failed: ' + url + '\n' + e)
131
+ return [
132
+ {
133
+ status: 502,
134
+ statusText: 'Fetch Error',
135
+ headers: new Headers({ 'Content-Type': 'text/plain' }),
136
+ },
137
+ new TextEncoder().encode(`Fetch ${url} failed:\n\n${err.stack}`)
138
+ .buffer,
139
+ ]
140
+ }
141
+ }
142
+
143
+ form_response_head(
144
+ status_code: number,
145
+ status_text: string,
146
+ headers: Headers,
147
+ ): Uint8Array {
148
+ const lines = [`HTTP/1.1 ${status_code} ${status_text}`]
149
+
150
+ for (const [key, value] of headers.entries()) {
151
+ lines.push(`${key}: ${value}`)
152
+ }
153
+
154
+ return new TextEncoder().encode(lines.join('\r\n') + '\r\n\r\n')
155
+ }
156
+
157
+ respond_text_and_close(
158
+ conn: TCPConnection,
159
+ status_code: number,
160
+ status_text: string,
161
+ body: string,
162
+ ): void {
163
+ const headers = new Headers({
164
+ 'content-type': 'text/plain',
165
+ 'content-length': body.length.toString(10),
166
+ connection: 'close',
167
+ })
168
+ conn.writev([
169
+ this.form_response_head(status_code, status_text, headers),
170
+ new TextEncoder().encode(body),
171
+ ])
172
+ conn.close()
173
+ }
174
+
175
+ parse_http_header(
176
+ header: string,
177
+ ): { key: string; value: string } | undefined {
178
+ const parts = header.match(/^([^:]*):(.*)$/)
179
+ if (!parts) {
180
+ dbg_log('Unable to parse HTTP header', LOG_FETCH)
181
+ return
182
+ }
183
+
184
+ const key = parts[1]
185
+ const value = parts[2].trim()
186
+
187
+ if (key.length === 0) {
188
+ dbg_log('Header key is empty, raw header', LOG_FETCH)
189
+ return
190
+ }
191
+ if (value.length === 0) {
192
+ dbg_log('Header value is empty', LOG_FETCH)
193
+ return
194
+ }
195
+ if (!/^[\w-]+$/.test(key)) {
196
+ dbg_log('Header key contains forbidden characters', LOG_FETCH)
197
+ return
198
+ }
199
+ if (!/^[\x20-\x7E]+$/.test(value)) {
200
+ dbg_log('Header value contains forbidden characters', LOG_FETCH)
201
+ return
202
+ }
203
+
204
+ return { key, value }
205
+ }
206
+
207
+ send(data: Uint8Array): void {
208
+ handle_fake_networking(data, this)
209
+ }
210
+
211
+ receive(data: Uint8Array): void {
212
+ this.bus.send('net' + this.id + '-receive', new Uint8Array(data))
213
+ }
214
+ }
215
+
216
+ /**
217
+ * HTTP data handler bound to a TCPConnection context.
218
+ */
219
+ async function on_data_http(
220
+ this: TCPConnection,
221
+ data: ArrayBuffer,
222
+ ): Promise<void> {
223
+ this.read = this.read || ''
224
+ this.read += new TextDecoder().decode(data)
225
+ if (this.read && this.read.indexOf('\r\n\r\n') !== -1) {
226
+ const offset = this.read.indexOf('\r\n\r\n')
227
+ const headers = this.read.substring(0, offset).split(/\r\n/)
228
+ const body = this.read.substring(offset + 4)
229
+ this.read = ''
230
+
231
+ const first_line = headers[0].split(' ')
232
+ let target: URL
233
+ if (/^https?:/.test(first_line[1])) {
234
+ // HTTP proxy
235
+ target = new URL(first_line[1])
236
+ } else {
237
+ target = new URL('http://host' + first_line[1])
238
+ }
239
+ if (
240
+ typeof window !== 'undefined' &&
241
+ target.protocol === 'http:' &&
242
+ window.location.protocol === 'https:'
243
+ ) {
244
+ // fix "Mixed Content" errors
245
+ target.protocol = 'https:'
246
+ }
247
+
248
+ const net = this.net as FetchNetworkAdapter
249
+
250
+ const req_headers = new Headers()
251
+ for (let i = 1; i < headers.length; ++i) {
252
+ const header = net.parse_http_header(headers[i])
253
+ if (!header) {
254
+ console.warn(
255
+ 'The request contains an invalid header: "%s"',
256
+ headers[i],
257
+ )
258
+ net.respond_text_and_close(
259
+ this,
260
+ 400,
261
+ 'Bad Request',
262
+ `Invalid header in request: ${headers[i]}`,
263
+ )
264
+ return
265
+ }
266
+ if (header.key.toLowerCase() === 'host') target.host = header.value
267
+ else req_headers.append(header.key, header.value)
268
+ }
269
+
270
+ if (!net.cors_proxy && /^\d+\.external$/.test(target.hostname)) {
271
+ dbg_log('Request to localhost: ' + target.href, LOG_FETCH)
272
+ const localport = parseInt(target.hostname.split('.')[0], 10)
273
+ if (!isNaN(localport) && localport > 0 && localport < 65536) {
274
+ target.protocol = 'http:'
275
+ target.hostname = 'localhost'
276
+ target.port = localport.toString(10)
277
+ } else {
278
+ console.warn('Unknown port for localhost: "%s"', target.href)
279
+ net.respond_text_and_close(
280
+ this,
281
+ 400,
282
+ 'Bad Request',
283
+ `Unknown port for localhost: ${target.href}`,
284
+ )
285
+ return
286
+ }
287
+ }
288
+
289
+ dbg_log('HTTP Dispatch: ' + target.href, LOG_FETCH)
290
+ this.name = target.href
291
+ const opts: RequestInit = {
292
+ method: first_line[0],
293
+ headers: req_headers,
294
+ }
295
+ if (['put', 'post'].indexOf(opts.method!.toLowerCase()) !== -1) {
296
+ opts.body = body
297
+ }
298
+
299
+ const fetch_url = net.cors_proxy
300
+ ? net.cors_proxy + encodeURIComponent(target.href)
301
+ : target.href
302
+ let response_started = false
303
+ const handler = (resp: Response) => {
304
+ const resp_headers = new Headers(resp.headers)
305
+ resp_headers.delete('content-encoding')
306
+ resp_headers.delete('keep-alive')
307
+ resp_headers.delete('content-length')
308
+ resp_headers.delete('transfer-encoding')
309
+ resp_headers.set('x-was-fetch-redirected', `${!!resp.redirected}`)
310
+ resp_headers.set('x-fetch-resp-url', resp.url)
311
+ resp_headers.set('connection', 'close')
312
+
313
+ this.write(
314
+ net.form_response_head(
315
+ resp.status,
316
+ resp.statusText,
317
+ resp_headers,
318
+ ),
319
+ )
320
+ response_started = true
321
+
322
+ if (resp.body && resp.body.getReader) {
323
+ const resp_reader = resp.body.getReader()
324
+ const pump = ({
325
+ value,
326
+ done,
327
+ }: ReadableStreamReadResult<Uint8Array>): void | Promise<void> => {
328
+ if (value) {
329
+ this.write(value)
330
+ }
331
+ if (done) {
332
+ this.close()
333
+ } else {
334
+ return resp_reader.read().then(pump)
335
+ }
336
+ }
337
+ resp_reader.read().then(pump)
338
+ } else {
339
+ resp.arrayBuffer().then((buffer) => {
340
+ this.write(new Uint8Array(buffer))
341
+ this.close()
342
+ })
343
+ }
344
+ }
345
+
346
+ fetch(fetch_url, opts)
347
+ .then(handler)
348
+ .catch((e) => {
349
+ console.warn('Fetch Failed: ' + fetch_url + '\n' + e)
350
+ if (!response_started) {
351
+ net.respond_text_and_close(
352
+ this,
353
+ 502,
354
+ 'Fetch Error',
355
+ `Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`,
356
+ )
357
+ }
358
+ this.close()
359
+ })
360
+ }
361
+ }
@@ -0,0 +1,124 @@
1
+ import { dbg_assert } from '../log.js'
2
+ import { load_file } from '../lib.js'
3
+
4
+ export interface FileStorageInterface {
5
+ read(
6
+ sha256sum: string,
7
+ offset: number,
8
+ count: number,
9
+ file_size: number,
10
+ ): Promise<Uint8Array | null>
11
+ cache(sha256sum: string, data: Uint8Array): Promise<void>
12
+ uncache(sha256sum: string): void
13
+ }
14
+
15
+ export class MemoryFileStorage implements FileStorageInterface {
16
+ filedata: Map<string, Uint8Array>
17
+
18
+ constructor() {
19
+ this.filedata = new Map()
20
+ }
21
+
22
+ async read(
23
+ sha256sum: string,
24
+ offset: number,
25
+ count: number,
26
+ _file_size: number,
27
+ ): Promise<Uint8Array | null> {
28
+ dbg_assert(
29
+ !!sha256sum,
30
+ 'MemoryFileStorage read: sha256sum should be a non-empty string',
31
+ )
32
+ const data = this.filedata.get(sha256sum)
33
+
34
+ if (!data) {
35
+ return null
36
+ }
37
+
38
+ return data.subarray(offset, offset + count)
39
+ }
40
+
41
+ async cache(sha256sum: string, data: Uint8Array): Promise<void> {
42
+ dbg_assert(
43
+ !!sha256sum,
44
+ 'MemoryFileStorage cache: sha256sum should be a non-empty string',
45
+ )
46
+ this.filedata.set(sha256sum, data)
47
+ }
48
+
49
+ uncache(sha256sum: string): void {
50
+ this.filedata.delete(sha256sum)
51
+ }
52
+ }
53
+
54
+ export class ServerFileStorageWrapper implements FileStorageInterface {
55
+ storage: FileStorageInterface
56
+ baseurl: string
57
+ zstd_decompress: (file_size: number, data: Uint8Array) => ArrayBuffer
58
+
59
+ constructor(
60
+ file_storage: FileStorageInterface,
61
+ baseurl: string,
62
+ zstd_decompress: (file_size: number, data: Uint8Array) => ArrayBuffer,
63
+ ) {
64
+ dbg_assert(
65
+ !!baseurl,
66
+ 'ServerMemoryFileStorage: baseurl should not be empty',
67
+ )
68
+
69
+ if (!baseurl.endsWith('/')) {
70
+ baseurl += '/'
71
+ }
72
+
73
+ this.storage = file_storage
74
+ this.baseurl = baseurl
75
+ this.zstd_decompress = zstd_decompress
76
+ }
77
+
78
+ load_from_server(
79
+ sha256sum: string,
80
+ file_size: number,
81
+ ): Promise<Uint8Array> {
82
+ return new Promise((resolve, _reject) => {
83
+ load_file(this.baseurl + sha256sum, {
84
+ done: async (buffer: ArrayBuffer) => {
85
+ let data = new Uint8Array(buffer)
86
+ if (sha256sum.endsWith('.zst')) {
87
+ data = new Uint8Array(
88
+ this.zstd_decompress(file_size, data),
89
+ )
90
+ }
91
+ await this.cache(sha256sum, data)
92
+ resolve(data)
93
+ },
94
+ })
95
+ })
96
+ }
97
+
98
+ async read(
99
+ sha256sum: string,
100
+ offset: number,
101
+ count: number,
102
+ file_size: number,
103
+ ): Promise<Uint8Array | null> {
104
+ const data = await this.storage.read(
105
+ sha256sum,
106
+ offset,
107
+ count,
108
+ file_size,
109
+ )
110
+ if (!data) {
111
+ const full_file = await this.load_from_server(sha256sum, file_size)
112
+ return full_file.subarray(offset, offset + count)
113
+ }
114
+ return data
115
+ }
116
+
117
+ async cache(sha256sum: string, data: Uint8Array): Promise<void> {
118
+ return await this.storage.cache(sha256sum, data)
119
+ }
120
+
121
+ uncache(sha256sum: string): void {
122
+ this.storage.uncache(sha256sum)
123
+ }
124
+ }
@@ -0,0 +1,57 @@
1
+ import { BusConnector } from '../bus.js'
2
+
3
+ interface InBrowserNetworkConfig {
4
+ id?: number
5
+ }
6
+
7
+ /**
8
+ * Network adapter "inbrowser" which connects the emulated NIC
9
+ * to a shared in-browser BroadcastChannel.
10
+ *
11
+ * NOTE: BroadcastChannel.postMessage() sends the given message to
12
+ * *other* BroadcastChannel objects set up for the same channel.
13
+ * Since we use the same BroadcastChannel instance for both
14
+ * sending and receiving we do not receive copies of our
15
+ * own sent messages.
16
+ * Source: https://html.spec.whatwg.org/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts
17
+ */
18
+ export class InBrowserNetworkAdapter {
19
+ bus: BusConnector
20
+ bus_send_msgid: string
21
+ bus_recv_msgid: string
22
+ channel: BroadcastChannel
23
+ is_open: boolean
24
+ nic_to_hub_fn: (eth_frame: Uint8Array) => void
25
+ hub_to_nic_fn: (ev: MessageEvent) => void
26
+
27
+ constructor(bus: BusConnector, config: InBrowserNetworkConfig) {
28
+ const id = config.id || 0
29
+
30
+ this.bus = bus
31
+ this.bus_send_msgid = `net${id}-send`
32
+ this.bus_recv_msgid = `net${id}-receive`
33
+ this.channel = new BroadcastChannel(`v86-inbrowser-${id}`)
34
+ this.is_open = true
35
+
36
+ // forward ethernet frames from emulated NIC to hub
37
+ this.nic_to_hub_fn = (eth_frame: Uint8Array) => {
38
+ this.channel.postMessage(eth_frame)
39
+ }
40
+ this.bus.register(this.bus_send_msgid, this.nic_to_hub_fn, this)
41
+
42
+ // forward ethernet frames from hub to emulated NIC
43
+ this.hub_to_nic_fn = (ev: MessageEvent) => {
44
+ this.bus.send(this.bus_recv_msgid, ev.data)
45
+ }
46
+ this.channel.addEventListener('message', this.hub_to_nic_fn)
47
+ }
48
+
49
+ destroy(): void {
50
+ if (this.is_open) {
51
+ this.bus.unregister(this.bus_send_msgid, this.nic_to_hub_fn)
52
+ this.channel.removeEventListener('message', this.hub_to_nic_fn)
53
+ this.channel.close()
54
+ this.is_open = false
55
+ }
56
+ }
57
+ }