@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.
- package/LICENSE +22 -0
- package/LICENSE.MIT +22 -0
- package/Readme.md +237 -0
- package/dist/v86.browser.js +26666 -0
- package/dist/v86.browser.js.map +7 -0
- package/dist/v86.js +26632 -0
- package/dist/v86.js.map +7 -0
- package/gen/generate_analyzer.ts +512 -0
- package/gen/generate_interpreter.ts +522 -0
- package/gen/generate_jit.ts +624 -0
- package/gen/rust_ast.ts +107 -0
- package/gen/util.ts +35 -0
- package/gen/x86_table.ts +1836 -0
- package/lib/9p.ts +1547 -0
- package/lib/filesystem.ts +1879 -0
- package/lib/marshall.ts +168 -0
- package/lib/softfloat/softfloat.c +32501 -0
- package/lib/zstd/zstddeclib.c +13520 -0
- package/package.json +75 -0
- package/src/acpi.ts +267 -0
- package/src/browser/dummy_screen.ts +106 -0
- package/src/browser/fake_network.ts +1771 -0
- package/src/browser/fetch_network.ts +361 -0
- package/src/browser/filestorage.ts +124 -0
- package/src/browser/inbrowser_network.ts +57 -0
- package/src/browser/keyboard.ts +564 -0
- package/src/browser/main.ts +3415 -0
- package/src/browser/mouse.ts +255 -0
- package/src/browser/network.ts +142 -0
- package/src/browser/print_stats.ts +336 -0
- package/src/browser/screen.ts +978 -0
- package/src/browser/serial.ts +316 -0
- package/src/browser/speaker.ts +1223 -0
- package/src/browser/starter.ts +1688 -0
- package/src/browser/wisp_network.ts +332 -0
- package/src/browser/worker_bus.ts +64 -0
- package/src/buffer.ts +652 -0
- package/src/bus.ts +78 -0
- package/src/const.ts +128 -0
- package/src/cpu.ts +2891 -0
- package/src/dma.ts +474 -0
- package/src/elf.ts +251 -0
- package/src/floppy.ts +1778 -0
- package/src/ide.ts +3455 -0
- package/src/io.ts +504 -0
- package/src/iso9660.ts +317 -0
- package/src/kernel.ts +250 -0
- package/src/lib.ts +645 -0
- package/src/log.ts +149 -0
- package/src/main.ts +199 -0
- package/src/ne2k.ts +1589 -0
- package/src/pci.ts +815 -0
- package/src/pit.ts +406 -0
- package/src/ps2.ts +820 -0
- package/src/rtc.ts +537 -0
- package/src/rust/analysis.rs +101 -0
- package/src/rust/codegen.rs +2660 -0
- package/src/rust/config.rs +3 -0
- package/src/rust/control_flow.rs +425 -0
- package/src/rust/cpu/apic.rs +658 -0
- package/src/rust/cpu/arith.rs +1207 -0
- package/src/rust/cpu/call_indirect.rs +2 -0
- package/src/rust/cpu/cpu.rs +4501 -0
- package/src/rust/cpu/fpu.rs +923 -0
- package/src/rust/cpu/global_pointers.rs +112 -0
- package/src/rust/cpu/instructions.rs +2486 -0
- package/src/rust/cpu/instructions_0f.rs +5261 -0
- package/src/rust/cpu/ioapic.rs +316 -0
- package/src/rust/cpu/memory.rs +351 -0
- package/src/rust/cpu/misc_instr.rs +613 -0
- package/src/rust/cpu/mod.rs +16 -0
- package/src/rust/cpu/modrm.rs +133 -0
- package/src/rust/cpu/pic.rs +402 -0
- package/src/rust/cpu/sse_instr.rs +361 -0
- package/src/rust/cpu/string.rs +701 -0
- package/src/rust/cpu/vga.rs +175 -0
- package/src/rust/cpu_context.rs +69 -0
- package/src/rust/dbg.rs +98 -0
- package/src/rust/gen/analyzer.rs +3807 -0
- package/src/rust/gen/analyzer0f.rs +3992 -0
- package/src/rust/gen/interpreter.rs +4447 -0
- package/src/rust/gen/interpreter0f.rs +5404 -0
- package/src/rust/gen/jit.rs +5080 -0
- package/src/rust/gen/jit0f.rs +5547 -0
- package/src/rust/gen/mod.rs +14 -0
- package/src/rust/jit.rs +2443 -0
- package/src/rust/jit_instructions.rs +7881 -0
- package/src/rust/js_api.rs +6 -0
- package/src/rust/leb.rs +46 -0
- package/src/rust/lib.rs +29 -0
- package/src/rust/modrm.rs +330 -0
- package/src/rust/opstats.rs +249 -0
- package/src/rust/page.rs +15 -0
- package/src/rust/paging.rs +25 -0
- package/src/rust/prefix.rs +15 -0
- package/src/rust/profiler.rs +155 -0
- package/src/rust/regs.rs +38 -0
- package/src/rust/softfloat.rs +286 -0
- package/src/rust/state_flags.rs +27 -0
- package/src/rust/wasmgen/mod.rs +2 -0
- package/src/rust/wasmgen/wasm_builder.rs +1047 -0
- package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
- package/src/rust/zstd.rs +105 -0
- package/src/sb16.ts +1928 -0
- package/src/state.ts +359 -0
- package/src/uart.ts +472 -0
- package/src/vga.ts +2791 -0
- package/src/virtio.ts +1756 -0
- package/src/virtio_balloon.ts +273 -0
- package/src/virtio_console.ts +372 -0
- 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
|
+
}
|